mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['react-app'],
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn'
|
||||||
|
},
|
||||||
|
ignorePatterns: ['android/**', 'build/**', 'node_modules/**', 'public/**', 'electron/**']
|
||||||
|
};
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
Generated
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
BIN
Binary file not shown.
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Vendored
+182
-115
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,69 +15,104 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
# This is normally unused
|
||||||
APP_BASE_NAME=`basename "$0"`
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -87,9 +122,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -98,88 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
ulimit -n $MAX_FD
|
warn "Could not query maximum file descriptor limit"
|
||||||
if [ $? -ne 0 ] ; then
|
esac
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
case $MAX_FD in #(
|
||||||
fi
|
'' | soft) :;; #(
|
||||||
else
|
*)
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
fi
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
Vendored
+21
-16
@@ -13,8 +13,10 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +27,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: path.resolve(__dirname, 'main.js'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="referrer" content="no-referrer" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="serverless, adminless, decentralized 4chan alternative" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<title>plebchan</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+31
-17
@@ -22,6 +22,8 @@
|
|||||||
"electron-context-menu": "3.3.0",
|
"electron-context-menu": "3.3.0",
|
||||||
"electron-is-dev": "2.0.0",
|
"electron-is-dev": "2.0.0",
|
||||||
"ext-name": "5.0.0",
|
"ext-name": "5.0.0",
|
||||||
|
"fs-extra": "11.3.0",
|
||||||
|
"http-proxy": "1.18.1",
|
||||||
"i18next": "23.5.1",
|
"i18next": "23.5.1",
|
||||||
"i18next-browser-languagedetector": "7.1.0",
|
"i18next-browser-languagedetector": "7.1.0",
|
||||||
"i18next-http-backend": "2.2.2",
|
"i18next-http-backend": "2.2.2",
|
||||||
@@ -29,14 +31,12 @@
|
|||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"memoizee": "0.4.15",
|
"memoizee": "0.4.15",
|
||||||
"prettier": "3.0.3",
|
"prettier": "3.0.3",
|
||||||
"react": "18.2.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "19.0.0",
|
||||||
"react-draggable": "4.4.6",
|
|
||||||
"react-i18next": "13.2.2",
|
"react-i18next": "13.2.2",
|
||||||
"react-markdown": "8.0.6",
|
"react-markdown": "8.0.6",
|
||||||
"react-router-dom": "6.16.0",
|
"react-router-dom": "6.16.0",
|
||||||
"react-router-hash-link": "2.4.3",
|
"react-router-hash-link": "2.4.3",
|
||||||
"react-scripts": "5.0.1",
|
|
||||||
"react-virtuoso": "4.12.3",
|
"react-virtuoso": "4.12.3",
|
||||||
"rehype-sanitize": "5.0.1",
|
"rehype-sanitize": "5.0.1",
|
||||||
"remark-gfm": "3.0.1",
|
"remark-gfm": "3.0.1",
|
||||||
@@ -46,12 +46,12 @@
|
|||||||
"zustand": "4.4.3"
|
"zustand": "4.4.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "vite",
|
||||||
"build": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=false react-scripts build",
|
"build": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=false vite build",
|
||||||
"build-netlify": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true REACT_APP_COMMIT_REF=$COMMIT_REF CI='' react-scripts build",
|
"build-netlify": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" PUBLIC_URL=./ GENERATE_SOURCEMAP=true VITE_COMMIT_REF=$COMMIT_REF CI='' vite build",
|
||||||
"test": "react-scripts test",
|
"test": "vitest",
|
||||||
"analyze-bundle": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true react-scripts build && source-map-explorer 'build/static/js/*.js'",
|
"preview": "vite preview",
|
||||||
"eject": "react-scripts eject",
|
"analyze-bundle": "cross-env PUBLIC_URL=./ GENERATE_SOURCEMAP=true vite build && source-map-explorer 'build/assets/*.js'",
|
||||||
"electron": "yarn electron:before && electron .",
|
"electron": "yarn electron:before && electron .",
|
||||||
"electron:no-delete-data": "yarn electron:before:download-ipfs && electron .",
|
"electron:no-delete-data": "yarn electron:before:download-ipfs && electron .",
|
||||||
"electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron\"",
|
"electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron\"",
|
||||||
@@ -64,13 +64,8 @@
|
|||||||
"electron:before:delete-data": "rimraf .plebbit",
|
"electron:before:delete-data": "rimraf .plebbit",
|
||||||
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
|
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
|
||||||
"prettier": "prettier {src,electron}/**/*.{js,ts,tsx} --write",
|
"prettier": "prettier {src,electron}/**/*.{js,ts,tsx} --write",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
||||||
},
|
"android:build": "yarn build && npx cap sync android && npx cap open android"
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -91,16 +86,35 @@
|
|||||||
"@capacitor/cli": "5.0.0",
|
"@capacitor/cli": "5.0.0",
|
||||||
"@capacitor/core": "5.0.0",
|
"@capacitor/core": "5.0.0",
|
||||||
"@types/memoizee": "0.4.9",
|
"@types/memoizee": "0.4.9",
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.26.0",
|
||||||
|
"@typescript-eslint/parser": "8.26.0",
|
||||||
|
"@vitejs/plugin-react": "4.3.4",
|
||||||
|
"assert": "2.1.0",
|
||||||
|
"babel-plugin-react-compiler": "19.0.0-beta-40c6c23-20250301",
|
||||||
|
"buffer": "6.0.3",
|
||||||
"concurrently": "8.0.1",
|
"concurrently": "8.0.1",
|
||||||
"conventional-changelog-cli": "4.1.0",
|
"conventional-changelog-cli": "4.1.0",
|
||||||
"cordova-res": "0.15.4",
|
"cordova-res": "0.15.4",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
|
"crypto-browserify": "3.12.1",
|
||||||
"cz-conventional-changelog": "3.3.0",
|
"cz-conventional-changelog": "3.3.0",
|
||||||
"decompress": "4.2.1",
|
"decompress": "4.2.1",
|
||||||
"electron": "28.2.3",
|
"electron": "28.2.3",
|
||||||
"electron-builder": "24.13.2",
|
"electron-builder": "24.13.2",
|
||||||
|
"electron-rebuild": "3.2.9",
|
||||||
|
"eslint": "8.56.0",
|
||||||
|
"eslint-config-react-app": "7.0.1",
|
||||||
|
"eslint-plugin-react": "7.37.4",
|
||||||
|
"eslint-plugin-react-compiler": "19.0.0-beta-40c6c23-20250301",
|
||||||
|
"eslint-plugin-react-hooks": "5.2.0",
|
||||||
"husky": "4.3.8",
|
"husky": "4.3.8",
|
||||||
|
"isomorphic-fetch": "3.0.0",
|
||||||
"lint-staged": "12.3.8",
|
"lint-staged": "12.3.8",
|
||||||
|
"stream-browserify": "3.0.0",
|
||||||
|
"vite": "6.2.0",
|
||||||
|
"vite-plugin-eslint": "1.8.1",
|
||||||
|
"vite-plugin-node-polyfills": "0.23.0",
|
||||||
|
"vite-plugin-pwa": "0.21.1",
|
||||||
"wait-on": "7.0.1"
|
"wait-on": "7.0.1"
|
||||||
},
|
},
|
||||||
"main": "electron/main.js",
|
"main": "electron/main.js",
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="referrer" content="no-referrer" />
|
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta name="description" content="serverless, adminless, decentralized 4chan alternative" />
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
|
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Preload assets -->
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/background-fade-blue.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/background-fade.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/button-fade-blue.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/button-fade-dark.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/button-fade.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/cross-blue.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/cross-dark.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/cross-photon.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/cross-red.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/minus-blue.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/minus-dark.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/minus-photon.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/minus-red.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/plus-blue.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/plus-dark.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/plus-photon.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/plus-red.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/question-blue.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/question-dark.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/question-photon.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/buttons/question-red.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/filedeleted-res.gif" as="image" type="image/gif" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/icons/closed.gif" as="image" type="image/gif" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/icons/offline-blue.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/icons/offline-red.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/icons/sticky.gif" as="image" type="image/gif" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/logo/logo-transparent.png" as="image" type="image/png" />
|
|
||||||
<link rel="preload" href="%PUBLIC_URL%/assets/spoiler.png" as="image" type="image/png" />
|
|
||||||
|
|
||||||
<title>plebchan</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "تم حذف هذا الموضوع بواسطة مؤلفه",
|
"this_thread_was_deleted": "تم حذف هذا الموضوع بواسطة مؤلفه",
|
||||||
"this_thread_was_removed": "تم إزالة هذا الموضوع بواسطة مشرفي المنتدى",
|
"this_thread_was_removed": "تم إزالة هذا الموضوع بواسطة مشرفي المنتدى",
|
||||||
"this_reply_was_deleted": "تم حذف هذه الرد من قبل مؤلفه",
|
"this_reply_was_deleted": "تم حذف هذه الرد من قبل مؤلفه",
|
||||||
"this_reply_was_removed": "تمت إزالة هذه الرد من قبل مشرفي المنتدى"
|
"this_reply_was_removed": "تمت إزالة هذه الرد من قبل مشرفي المنتدى",
|
||||||
|
"not_mod_of_any_board": "أنت لست مشرفًا في أي لوحة."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "এই থ্রেডটি এর লেখক দ্বারা মুছে ফেলা হয়েছে",
|
"this_thread_was_deleted": "এই থ্রেডটি এর লেখক দ্বারা মুছে ফেলা হয়েছে",
|
||||||
"this_thread_was_removed": "এই থ্রেডটি বোর্ড moderators দ্বারা মুছে ফেলা হয়েছে",
|
"this_thread_was_removed": "এই থ্রেডটি বোর্ড moderators দ্বারা মুছে ফেলা হয়েছে",
|
||||||
"this_reply_was_deleted": "এই প্রতিক্রিয়া তার লেখক দ্বারা মুছে ফেলা হয়েছে",
|
"this_reply_was_deleted": "এই প্রতিক্রিয়া তার লেখক দ্বারা মুছে ফেলা হয়েছে",
|
||||||
"this_reply_was_removed": "এই প্রতিক্রিয়া বোর্ড পরিচালকদের দ্বারা সরানো হয়েছে"
|
"this_reply_was_removed": "এই প্রতিক্রিয়া বোর্ড পরিচালকদের দ্বারা সরানো হয়েছে",
|
||||||
|
"not_mod_of_any_board": "আপনি কোনও বোর্ডের মড নন।"
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Toto vlákno bylo smazáno jeho autorem",
|
"this_thread_was_deleted": "Toto vlákno bylo smazáno jeho autorem",
|
||||||
"this_thread_was_removed": "Toto vlákno bylo odstraněno moderátory fóra",
|
"this_thread_was_removed": "Toto vlákno bylo odstraněno moderátory fóra",
|
||||||
"this_reply_was_deleted": "Tato odpověď byla smazána autorem",
|
"this_reply_was_deleted": "Tato odpověď byla smazána autorem",
|
||||||
"this_reply_was_removed": "Tato odpověď byla odstraněna moderátory fóra"
|
"this_reply_was_removed": "Tato odpověď byla odstraněna moderátory fóra",
|
||||||
|
"not_mod_of_any_board": "Nejste moderátor žádné desky."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Denne tråd blev slettet af dens forfatter",
|
"this_thread_was_deleted": "Denne tråd blev slettet af dens forfatter",
|
||||||
"this_thread_was_removed": "Denne tråd blev fjernet af boardmoderatorer",
|
"this_thread_was_removed": "Denne tråd blev fjernet af boardmoderatorer",
|
||||||
"this_reply_was_deleted": "Dette svar blev slettet af forfatteren",
|
"this_reply_was_deleted": "Dette svar blev slettet af forfatteren",
|
||||||
"this_reply_was_removed": "Dette svar blev fjernet af forummoderatorer"
|
"this_reply_was_removed": "Dette svar blev fjernet af forummoderatorer",
|
||||||
|
"not_mod_of_any_board": "Du er ikke moderator for nogen tavler."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Dieser Thread wurde von seinem Autor gelöscht",
|
"this_thread_was_deleted": "Dieser Thread wurde von seinem Autor gelöscht",
|
||||||
"this_thread_was_removed": "Dieser Thread wurde von den Moderatoren des Boards entfernt",
|
"this_thread_was_removed": "Dieser Thread wurde von den Moderatoren des Boards entfernt",
|
||||||
"this_reply_was_deleted": "Diese Antwort wurde vom Autor gelöscht",
|
"this_reply_was_deleted": "Diese Antwort wurde vom Autor gelöscht",
|
||||||
"this_reply_was_removed": "Diese Antwort wurde von den Forenmoderatoren entfernt"
|
"this_reply_was_removed": "Diese Antwort wurde von den Forenmoderatoren entfernt",
|
||||||
|
"not_mod_of_any_board": "Du bist kein Moderator eines Boards."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Αυτή η συζήτηση διαγράφηκε από τον συγγραφέα της",
|
"this_thread_was_deleted": "Αυτή η συζήτηση διαγράφηκε από τον συγγραφέα της",
|
||||||
"this_thread_was_removed": "Αυτή η συζήτηση αφαιρέθηκε από τους διαχειριστές του φόρουμ",
|
"this_thread_was_removed": "Αυτή η συζήτηση αφαιρέθηκε από τους διαχειριστές του φόρουμ",
|
||||||
"this_reply_was_deleted": "Αυτή η απάντηση διαγράφηκε από τον συγγραφέα της",
|
"this_reply_was_deleted": "Αυτή η απάντηση διαγράφηκε από τον συγγραφέα της",
|
||||||
"this_reply_was_removed": "Η απάντηση αυτή αφαιρέθηκε από τους διαχειριστές του φόρουμ"
|
"this_reply_was_removed": "Η απάντηση αυτή αφαιρέθηκε από τους διαχειριστές του φόρουμ",
|
||||||
|
"not_mod_of_any_board": "Δεν είστε διαχειριστής σε καμία κοινότητα."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "This thread was deleted by its author",
|
"this_thread_was_deleted": "This thread was deleted by its author",
|
||||||
"this_thread_was_removed": "This thread was removed by board moderators",
|
"this_thread_was_removed": "This thread was removed by board moderators",
|
||||||
"this_reply_was_deleted": "This reply was deleted by its author",
|
"this_reply_was_deleted": "This reply was deleted by its author",
|
||||||
"this_reply_was_removed": "This reply was removed by board moderators"
|
"this_reply_was_removed": "This reply was removed by board moderators",
|
||||||
|
"not_mod_of_any_board": "You are not a mod of any board."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Este hilo fue eliminado por su autor",
|
"this_thread_was_deleted": "Este hilo fue eliminado por su autor",
|
||||||
"this_thread_was_removed": "Este hilo fue eliminado por los moderadores del foro",
|
"this_thread_was_removed": "Este hilo fue eliminado por los moderadores del foro",
|
||||||
"this_reply_was_deleted": "Esta respuesta fue eliminada por su autor",
|
"this_reply_was_deleted": "Esta respuesta fue eliminada por su autor",
|
||||||
"this_reply_was_removed": "Esta respuesta fue eliminada por los moderadores del foro"
|
"this_reply_was_removed": "Esta respuesta fue eliminada por los moderadores del foro",
|
||||||
|
"not_mod_of_any_board": "No eres moderador de ningún tablero."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "این موضوع توسط نویسندهاش حذف شد",
|
"this_thread_was_deleted": "این موضوع توسط نویسندهاش حذف شد",
|
||||||
"this_thread_was_removed": "این تاپیک توسط مدیران انجمن حذف شد",
|
"this_thread_was_removed": "این تاپیک توسط مدیران انجمن حذف شد",
|
||||||
"this_reply_was_deleted": "این پاسخ توسط نویسندهاش حذف شد",
|
"this_reply_was_deleted": "این پاسخ توسط نویسندهاش حذف شد",
|
||||||
"this_reply_was_removed": "این پاسخ توسط مدیران انجمن حذف شد"
|
"this_reply_was_removed": "این پاسخ توسط مدیران انجمن حذف شد",
|
||||||
|
"not_mod_of_any_board": "شما هیچ تابلو مدیر نیستید."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Tämä ketju poistettiin sen kirjoittajan toimesta",
|
"this_thread_was_deleted": "Tämä ketju poistettiin sen kirjoittajan toimesta",
|
||||||
"this_thread_was_removed": "Tämä ketju poistettiin foorumin moderaattoreiden toimesta",
|
"this_thread_was_removed": "Tämä ketju poistettiin foorumin moderaattoreiden toimesta",
|
||||||
"this_reply_was_deleted": "Tämä vastaus poistettiin sen kirjoittajalta",
|
"this_reply_was_deleted": "Tämä vastaus poistettiin sen kirjoittajalta",
|
||||||
"this_reply_was_removed": "Tämä vastaus poistettiin foorumin moderaattoreiden toimesta"
|
"this_reply_was_removed": "Tämä vastaus poistettiin foorumin moderaattoreiden toimesta",
|
||||||
|
"not_mod_of_any_board": "Et ole moderaattori missään taulussa."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Ang thread na ito ay tinanggal ng may-akda nito",
|
"this_thread_was_deleted": "Ang thread na ito ay tinanggal ng may-akda nito",
|
||||||
"this_thread_was_removed": "Inalis ang thread na ito ng mga moderator ng board",
|
"this_thread_was_removed": "Inalis ang thread na ito ng mga moderator ng board",
|
||||||
"this_reply_was_deleted": "Ang tugon na ito ay tinanggal ng may-akda nito",
|
"this_reply_was_deleted": "Ang tugon na ito ay tinanggal ng may-akda nito",
|
||||||
"this_reply_was_removed": "Ang sagot na ito ay tinanggal ng mga moderator ng board"
|
"this_reply_was_removed": "Ang sagot na ito ay tinanggal ng mga moderator ng board",
|
||||||
|
"not_mod_of_any_board": "Hindi ka mod ng anumang board."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Ce sujet a été supprimé par son auteur",
|
"this_thread_was_deleted": "Ce sujet a été supprimé par son auteur",
|
||||||
"this_thread_was_removed": "Ce sujet a été supprimé par les modérateurs du forum",
|
"this_thread_was_removed": "Ce sujet a été supprimé par les modérateurs du forum",
|
||||||
"this_reply_was_deleted": "Cette réponse a été supprimée par son auteur",
|
"this_reply_was_deleted": "Cette réponse a été supprimée par son auteur",
|
||||||
"this_reply_was_removed": "Cette réponse a été supprimée par les modérateurs du forum"
|
"this_reply_was_removed": "Cette réponse a été supprimée par les modérateurs du forum",
|
||||||
|
"not_mod_of_any_board": "Vous n'êtes pas modérateur de n'aucun forum."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "השרשור הזה נמחק על ידי המחבר שלו",
|
"this_thread_was_deleted": "השרשור הזה נמחק על ידי המחבר שלו",
|
||||||
"this_thread_was_removed": "השרשור הזה הוסר על ידי מנהלי הפורום",
|
"this_thread_was_removed": "השרשור הזה הוסר על ידי מנהלי הפורום",
|
||||||
"this_reply_was_deleted": "תשובה זו נמחקה על ידי המחבר שלה",
|
"this_reply_was_deleted": "תשובה זו נמחקה על ידי המחבר שלה",
|
||||||
"this_reply_was_removed": "תשובה זו הוסרה על ידי מנהלי הפורום"
|
"this_reply_was_removed": "תשובה זו הוסרה על ידי מנהלי הפורום",
|
||||||
|
"not_mod_of_any_board": "אתה לא מוד בכל לוח."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "इस थ्रेड को इसके लेखक ने हटा दिया है",
|
"this_thread_was_deleted": "इस थ्रेड को इसके लेखक ने हटा दिया है",
|
||||||
"this_thread_was_removed": "यह थ्रेड बोर्ड मॉडरेटर्स द्वारा हटा दिया गया था",
|
"this_thread_was_removed": "यह थ्रेड बोर्ड मॉडरेटर्स द्वारा हटा दिया गया था",
|
||||||
"this_reply_was_deleted": "इस उत्तर को इसके लेखक द्वारा हटा दिया गया था",
|
"this_reply_was_deleted": "इस उत्तर को इसके लेखक द्वारा हटा दिया गया था",
|
||||||
"this_reply_was_removed": "यह उत्तर बोर्ड मॉडरेटर द्वारा हटा दिया गया था"
|
"this_reply_was_removed": "यह उत्तर बोर्ड मॉडरेटर द्वारा हटा दिया गया था",
|
||||||
|
"not_mod_of_any_board": "आप किसी बोर्ड के मॉड नहीं हैं।"
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Ez a szál törlésre került a szerzője által",
|
"this_thread_was_deleted": "Ez a szál törlésre került a szerzője által",
|
||||||
"this_thread_was_removed": "Ezt a témát a fórum moderátorai eltávolították",
|
"this_thread_was_removed": "Ezt a témát a fórum moderátorai eltávolították",
|
||||||
"this_reply_was_deleted": "Ez a válasz törölve lett a szerzője által",
|
"this_reply_was_deleted": "Ez a válasz törölve lett a szerzője által",
|
||||||
"this_reply_was_removed": "Ez a válasz törlésre került a fórum moderátorai által"
|
"this_reply_was_removed": "Ez a válasz törlésre került a fórum moderátorai által",
|
||||||
|
"not_mod_of_any_board": "Nem vagy moderátor egyetlen táblán sem."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Thread ini dihapus oleh penulisnya",
|
"this_thread_was_deleted": "Thread ini dihapus oleh penulisnya",
|
||||||
"this_thread_was_removed": "Thread ini dihapus oleh moderator papan",
|
"this_thread_was_removed": "Thread ini dihapus oleh moderator papan",
|
||||||
"this_reply_was_deleted": "Balasan ini dihapus oleh penulisnya",
|
"this_reply_was_deleted": "Balasan ini dihapus oleh penulisnya",
|
||||||
"this_reply_was_removed": "Balasan ini dihapus oleh moderator papan"
|
"this_reply_was_removed": "Balasan ini dihapus oleh moderator papan",
|
||||||
|
"not_mod_of_any_board": "Anda bukan mod di papan manapun."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Questo thread è stato eliminato dal suo autore",
|
"this_thread_was_deleted": "Questo thread è stato eliminato dal suo autore",
|
||||||
"this_thread_was_removed": "Questo thread è stato rimosso dai moderatori del board",
|
"this_thread_was_removed": "Questo thread è stato rimosso dai moderatori del board",
|
||||||
"this_reply_was_deleted": "Questa risposta è stata eliminata dal suo autore",
|
"this_reply_was_deleted": "Questa risposta è stata eliminata dal suo autore",
|
||||||
"this_reply_was_removed": "Questa risposta è stata rimossa dai moderatori del forum"
|
"this_reply_was_removed": "Questa risposta è stata rimossa dai moderatori del forum",
|
||||||
|
"not_mod_of_any_board": "Non sei un mod di nessuna board."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "このスレッドは著者によって削除されました",
|
"this_thread_was_deleted": "このスレッドは著者によって削除されました",
|
||||||
"this_thread_was_removed": "このスレッドはボードのモデレーターによって削除されました",
|
"this_thread_was_removed": "このスレッドはボードのモデレーターによって削除されました",
|
||||||
"this_reply_was_deleted": "この返信はその著者によって削除されました",
|
"this_reply_was_deleted": "この返信はその著者によって削除されました",
|
||||||
"this_reply_was_removed": "この返信は掲示板のモデレーターによって削除されました"
|
"this_reply_was_removed": "この返信は掲示板のモデレーターによって削除されました",
|
||||||
|
"not_mod_of_any_board": "あなたはどのボードのモデレーターでもありません。"
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "이 스레드는 작성자에 의해 삭제되었습니다",
|
"this_thread_was_deleted": "이 스레드는 작성자에 의해 삭제되었습니다",
|
||||||
"this_thread_was_removed": "이 스레드는 보드 관리자에 의해 제거되었습니다",
|
"this_thread_was_removed": "이 스레드는 보드 관리자에 의해 제거되었습니다",
|
||||||
"this_reply_was_deleted": "이 답글은 작성자에 의해 삭제되었습니다",
|
"this_reply_was_deleted": "이 답글은 작성자에 의해 삭제되었습니다",
|
||||||
"this_reply_was_removed": "이 답글은 게시판 관리자에 의해 삭제되었습니다"
|
"this_reply_was_removed": "이 답글은 게시판 관리자에 의해 삭제되었습니다",
|
||||||
|
"not_mod_of_any_board": "당신은 어떤 게시판의 관리자도 아닙니다."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "हा धागा त्याच्या लेखकाने हटविला",
|
"this_thread_was_deleted": "हा धागा त्याच्या लेखकाने हटविला",
|
||||||
"this_thread_was_removed": "हे थ्रेड बोर्ड मध्यमाने काढले गेले आहे",
|
"this_thread_was_removed": "हे थ्रेड बोर्ड मध्यमाने काढले गेले आहे",
|
||||||
"this_reply_was_deleted": "हा प्रतिसाद त्याच्या लेखकाने हटवला आहे",
|
"this_reply_was_deleted": "हा प्रतिसाद त्याच्या लेखकाने हटवला आहे",
|
||||||
"this_reply_was_removed": "या उत्तराला बोर्ड मॉडरेटरने काढले आहे"
|
"this_reply_was_removed": "या उत्तराला बोर्ड मॉडरेटरने काढले आहे",
|
||||||
|
"not_mod_of_any_board": "तुम्ही कोणत्याही बोर्डचे मॉड नाहीत."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Dit draadje is verwijderd door de auteur",
|
"this_thread_was_deleted": "Dit draadje is verwijderd door de auteur",
|
||||||
"this_thread_was_removed": "Deze thread werd verwijderd door boardmoderators",
|
"this_thread_was_removed": "Deze thread werd verwijderd door boardmoderators",
|
||||||
"this_reply_was_deleted": "Dit antwoord werd verwijderd door de auteur",
|
"this_reply_was_deleted": "Dit antwoord werd verwijderd door de auteur",
|
||||||
"this_reply_was_removed": "Dit antwoord is verwijderd door de forum moderators"
|
"this_reply_was_removed": "Dit antwoord is verwijderd door de forum moderators",
|
||||||
|
"not_mod_of_any_board": "Je bent geen mod van een bord."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Denne tråden ble slettet av forfatteren",
|
"this_thread_was_deleted": "Denne tråden ble slettet av forfatteren",
|
||||||
"this_thread_was_removed": "Denne tråden ble fjernet av boardmoderatorer",
|
"this_thread_was_removed": "Denne tråden ble fjernet av boardmoderatorer",
|
||||||
"this_reply_was_deleted": "Dette svaret ble slettet av forfatteren",
|
"this_reply_was_deleted": "Dette svaret ble slettet av forfatteren",
|
||||||
"this_reply_was_removed": "Dette svaret ble fjernet av forummoderatorer"
|
"this_reply_was_removed": "Dette svaret ble fjernet av forummoderatorer",
|
||||||
|
"not_mod_of_any_board": "Du er ikke moderator på noen brett."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Wątek ten został usunięty przez jego autora",
|
"this_thread_was_deleted": "Wątek ten został usunięty przez jego autora",
|
||||||
"this_thread_was_removed": "Wątek został usunięty przez moderatorów forum",
|
"this_thread_was_removed": "Wątek został usunięty przez moderatorów forum",
|
||||||
"this_reply_was_deleted": "Ta odpowiedź została usunięta przez autora",
|
"this_reply_was_deleted": "Ta odpowiedź została usunięta przez autora",
|
||||||
"this_reply_was_removed": "Ta odpowiedź została usunięta przez moderatorów forum"
|
"this_reply_was_removed": "Ta odpowiedź została usunięta przez moderatorów forum",
|
||||||
|
"not_mod_of_any_board": "Nie jesteś moderatorem żadnej tablicy."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Este tópico foi excluído pelo seu autor",
|
"this_thread_was_deleted": "Este tópico foi excluído pelo seu autor",
|
||||||
"this_thread_was_removed": "Este tópico foi removido pelos moderadores do fórum",
|
"this_thread_was_removed": "Este tópico foi removido pelos moderadores do fórum",
|
||||||
"this_reply_was_deleted": "Esta resposta foi excluída pelo autor",
|
"this_reply_was_deleted": "Esta resposta foi excluída pelo autor",
|
||||||
"this_reply_was_removed": "Esta resposta foi removida pelos moderadores do fórum"
|
"this_reply_was_removed": "Esta resposta foi removida pelos moderadores do fórum",
|
||||||
|
"not_mod_of_any_board": "Você não é um moderador de nenhum quadro."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Acest thread a fost șters de autorul său",
|
"this_thread_was_deleted": "Acest thread a fost șters de autorul său",
|
||||||
"this_thread_was_removed": "Acest thread a fost eliminat de moderatorii board-ului",
|
"this_thread_was_removed": "Acest thread a fost eliminat de moderatorii board-ului",
|
||||||
"this_reply_was_deleted": "Acest răspuns a fost șters de autorul său",
|
"this_reply_was_deleted": "Acest răspuns a fost șters de autorul său",
|
||||||
"this_reply_was_removed": "Acest răspuns a fost eliminat de moderatorii forumului"
|
"this_reply_was_removed": "Acest răspuns a fost eliminat de moderatorii forumului",
|
||||||
|
"not_mod_of_any_board": "Nu ești moderator pe niciun tablou."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Эта тема была удалена её автором",
|
"this_thread_was_deleted": "Эта тема была удалена её автором",
|
||||||
"this_thread_was_removed": "Эта тема была удалена модераторами форума",
|
"this_thread_was_removed": "Эта тема была удалена модераторами форума",
|
||||||
"this_reply_was_deleted": "Этот ответ был удален автором",
|
"this_reply_was_deleted": "Этот ответ был удален автором",
|
||||||
"this_reply_was_removed": "Этот ответ был удален модераторами форума"
|
"this_reply_was_removed": "Этот ответ был удален модераторами форума",
|
||||||
|
"not_mod_of_any_board": "Вы не модератор ни одной доски."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Ky thread u fshi nga autori i tij",
|
"this_thread_was_deleted": "Ky thread u fshi nga autori i tij",
|
||||||
"this_thread_was_removed": "Ky thread u hoq nga moderatorët e bordit",
|
"this_thread_was_removed": "Ky thread u hoq nga moderatorët e bordit",
|
||||||
"this_reply_was_deleted": "Ky përgjigje u fshi nga autori i saj",
|
"this_reply_was_deleted": "Ky përgjigje u fshi nga autori i saj",
|
||||||
"this_reply_was_removed": "Ky përgjigje u hoq nga moderatorët e forumit"
|
"this_reply_was_removed": "Ky përgjigje u hoq nga moderatorët e forumit",
|
||||||
|
"not_mod_of_any_board": "Nuk jeni mod në asnjë bord."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Denna tråd raderades av dess författare",
|
"this_thread_was_deleted": "Denna tråd raderades av dess författare",
|
||||||
"this_thread_was_removed": "Denna tråd togs bort av forumets moderatorer",
|
"this_thread_was_removed": "Denna tråd togs bort av forumets moderatorer",
|
||||||
"this_reply_was_deleted": "Detta svar togs bort av författaren",
|
"this_reply_was_deleted": "Detta svar togs bort av författaren",
|
||||||
"this_reply_was_removed": "Detta svar togs bort av forumets moderatorer"
|
"this_reply_was_removed": "Detta svar togs bort av forumets moderatorer",
|
||||||
|
"not_mod_of_any_board": "Du är inte moderator på någon tavla."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "ఈ థ్రెడ్ను దాని రచయిత తొలగించారు",
|
"this_thread_was_deleted": "ఈ థ్రెడ్ను దాని రచయిత తొలగించారు",
|
||||||
"this_thread_was_removed": "ఈ థ్రెడ్ బోర్డ్ మోడరేటర్ల ద్వారా తీసివేయబడింది",
|
"this_thread_was_removed": "ఈ థ్రెడ్ బోర్డ్ మోడరేటర్ల ద్వారా తీసివేయబడింది",
|
||||||
"this_reply_was_deleted": "ఈ స్పందనను దాని రచయిత తొలగించారు",
|
"this_reply_was_deleted": "ఈ స్పందనను దాని రచయిత తొలగించారు",
|
||||||
"this_reply_was_removed": "ఈ స్పందన బోర్డ్ మోడరేటర్లచే తొలగించబడింది"
|
"this_reply_was_removed": "ఈ స్పందన బోర్డ్ మోడరేటర్లచే తొలగించబడింది",
|
||||||
|
"not_mod_of_any_board": "మీరు ఎటువంటి బోర్డుకు మోడ్ కంటే లేరు."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "กระทู้นี้ถูกลบโดยผู้แต่ง",
|
"this_thread_was_deleted": "กระทู้นี้ถูกลบโดยผู้แต่ง",
|
||||||
"this_thread_was_removed": "กระทู้นี้ถูกลบโดยผู้ดูแลบอร์ด",
|
"this_thread_was_removed": "กระทู้นี้ถูกลบโดยผู้ดูแลบอร์ด",
|
||||||
"this_reply_was_deleted": "การตอบกลับนี้ถูกลบโดยผู้แต่ง",
|
"this_reply_was_deleted": "การตอบกลับนี้ถูกลบโดยผู้แต่ง",
|
||||||
"this_reply_was_removed": "คำตอบนี้ถูกลบโดยผู้ดูแลบอร์ด"
|
"this_reply_was_removed": "คำตอบนี้ถูกลบโดยผู้ดูแลบอร์ด",
|
||||||
|
"not_mod_of_any_board": "คุณไม่ใช่ผู้ดูแลบอร์ดใดๆ"
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Bu başlık, yazarının tarafından silindi",
|
"this_thread_was_deleted": "Bu başlık, yazarının tarafından silindi",
|
||||||
"this_thread_was_removed": "Bu konu, board moderatörleri tarafından kaldırıldı",
|
"this_thread_was_removed": "Bu konu, board moderatörleri tarafından kaldırıldı",
|
||||||
"this_reply_was_deleted": "Bu yanıt, yazar tarafından silindi",
|
"this_reply_was_deleted": "Bu yanıt, yazar tarafından silindi",
|
||||||
"this_reply_was_removed": "Bu yanıt, forum moderatörleri tarafından kaldırıldı"
|
"this_reply_was_removed": "Bu yanıt, forum moderatörleri tarafından kaldırıldı",
|
||||||
|
"not_mod_of_any_board": "Hiçbir tahtanın moderatörü değilsiniz."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Цю тему видалив її автор",
|
"this_thread_was_deleted": "Цю тему видалив її автор",
|
||||||
"this_thread_was_removed": "Цю тему було видалено модераторами форуму",
|
"this_thread_was_removed": "Цю тему було видалено модераторами форуму",
|
||||||
"this_reply_was_deleted": "Цю відповідь було видалено її автором",
|
"this_reply_was_deleted": "Цю відповідь було видалено її автором",
|
||||||
"this_reply_was_removed": "Цю відповідь було видалено модераторами форуму"
|
"this_reply_was_removed": "Цю відповідь було видалено модераторами форуму",
|
||||||
|
"not_mod_of_any_board": "Ви не модератор жодної дошки."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "اس تھریڈ کو اس کے مصنف نے حذف کیا",
|
"this_thread_was_deleted": "اس تھریڈ کو اس کے مصنف نے حذف کیا",
|
||||||
"this_thread_was_removed": "اس تھریڈ کو بورڈ کے موڈریٹرز نے ہٹا دیا ہے",
|
"this_thread_was_removed": "اس تھریڈ کو بورڈ کے موڈریٹرز نے ہٹا دیا ہے",
|
||||||
"this_reply_was_deleted": "اس جواب کو اس کے مصنف نے حذف کر دیا تھا",
|
"this_reply_was_deleted": "اس جواب کو اس کے مصنف نے حذف کر دیا تھا",
|
||||||
"this_reply_was_removed": "اس جواب کو بورڈ کے منتظمین نے حذف کر دیا ہے"
|
"this_reply_was_removed": "اس جواب کو بورڈ کے منتظمین نے حذف کر دیا ہے",
|
||||||
|
"not_mod_of_any_board": "آپ کسی بورڈ کے موڈ نہیں ہیں۔"
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "Chủ đề này đã bị tác giả của nó xóa",
|
"this_thread_was_deleted": "Chủ đề này đã bị tác giả của nó xóa",
|
||||||
"this_thread_was_removed": "Chủ đề này đã bị các quản trị viên diễn đàn xóa",
|
"this_thread_was_removed": "Chủ đề này đã bị các quản trị viên diễn đàn xóa",
|
||||||
"this_reply_was_deleted": "Câu trả lời này đã bị tác giả của nó xóa",
|
"this_reply_was_deleted": "Câu trả lời này đã bị tác giả của nó xóa",
|
||||||
"this_reply_was_removed": "Phản hồi này đã bị xóa bởi các quản trị viên diễn đàn"
|
"this_reply_was_removed": "Phản hồi này đã bị xóa bởi các quản trị viên diễn đàn",
|
||||||
|
"not_mod_of_any_board": "Bạn không phải là quản trị viên của bất kỳ bảng nào."
|
||||||
}
|
}
|
||||||
@@ -290,5 +290,6 @@
|
|||||||
"this_thread_was_deleted": "该线程已被其作者删除",
|
"this_thread_was_deleted": "该线程已被其作者删除",
|
||||||
"this_thread_was_removed": "此帖子已被论坛版主删除",
|
"this_thread_was_removed": "此帖子已被论坛版主删除",
|
||||||
"this_reply_was_deleted": "该回复已被其作者删除",
|
"this_reply_was_deleted": "该回复已被其作者删除",
|
||||||
"this_reply_was_removed": "该回复已被版主删除"
|
"this_reply_was_removed": "该回复已被版主删除",
|
||||||
|
"not_mod_of_any_board": "您不是任何板块的管理员。"
|
||||||
}
|
}
|
||||||
+25
-1
@@ -3,6 +3,8 @@ import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom'
|
|||||||
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import { initSnow, removeSnow } from './lib/snow';
|
import { initSnow, removeSnow } from './lib/snow';
|
||||||
import { isAllView, isSubscriptionsView } from './lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView } from './lib/utils/view-utils';
|
||||||
|
import useReplyModalStore from './stores/use-reply-modal-store';
|
||||||
|
import useSpecialThemeStore from './stores/use-special-theme-store';
|
||||||
import useIsMobile from './hooks/use-is-mobile';
|
import useIsMobile from './hooks/use-is-mobile';
|
||||||
import useTheme from './hooks/use-theme';
|
import useTheme from './hooks/use-theme';
|
||||||
import styles from './app.module.css';
|
import styles from './app.module.css';
|
||||||
@@ -16,10 +18,11 @@ import Post from './views/post';
|
|||||||
import { DesktopBoardButtons, MobileBoardButtons } from './components/board-buttons';
|
import { DesktopBoardButtons, MobileBoardButtons } from './components/board-buttons';
|
||||||
import BoardHeader from './components/board-header';
|
import BoardHeader from './components/board-header';
|
||||||
import ChallengeModal from './components/challenge-modal';
|
import ChallengeModal from './components/challenge-modal';
|
||||||
|
import ReplyModal from './components/reply-modal';
|
||||||
import PostForm from './components/post-form';
|
import PostForm from './components/post-form';
|
||||||
import SubplebbitStats from './components/subplebbit-stats';
|
import SubplebbitStats from './components/subplebbit-stats';
|
||||||
import TopBar from './components/topbar';
|
import TopBar from './components/topbar';
|
||||||
import useSpecialThemeStore from './stores/use-special-theme-store';
|
import SettingsModal from './components/settings-modal';
|
||||||
|
|
||||||
const BoardLayout = () => {
|
const BoardLayout = () => {
|
||||||
const { accountCommentIndex, subplebbitAddress } = useParams();
|
const { accountCommentIndex, subplebbitAddress } = useParams();
|
||||||
@@ -87,9 +90,25 @@ const GlobalLayout = () => {
|
|||||||
}
|
}
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
const { activeCid, threadCid, subplebbitAddress, closeModal, showReplyModal, scrollY } = useReplyModalStore();
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
const isInSettingsView = location.pathname.endsWith('/settings');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChallengeModal />
|
<ChallengeModal />
|
||||||
|
{activeCid && threadCid && subplebbitAddress && (
|
||||||
|
<ReplyModal
|
||||||
|
closeModal={closeModal}
|
||||||
|
parentCid={activeCid}
|
||||||
|
postCid={threadCid}
|
||||||
|
scrollY={scrollY}
|
||||||
|
showReplyModal={showReplyModal}
|
||||||
|
subplebbitAddress={subplebbitAddress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isInSettingsView && <SettingsModal />}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -128,6 +147,11 @@ const App = () => {
|
|||||||
<Route path='/p/subscriptions/catalog/:timeFilterName?' element={<Catalog />} />
|
<Route path='/p/subscriptions/catalog/:timeFilterName?' element={<Catalog />} />
|
||||||
<Route path='/p/subscriptions/catalog/:timeFilterName?/settings' element={<Catalog />} />
|
<Route path='/p/subscriptions/catalog/:timeFilterName?/settings' element={<Catalog />} />
|
||||||
|
|
||||||
|
<Route path='/p/mod/:timeFilterName?' element={<Board />} />
|
||||||
|
<Route path='/p/mod/:timeFilterName?/settings' element={<Board />} />
|
||||||
|
<Route path='/p/mod/catalog/:timeFilterName?' element={<Catalog />} />
|
||||||
|
<Route path='/p/mod/catalog/:timeFilterName?/settings' element={<Catalog />} />
|
||||||
|
|
||||||
<Route path='/profile/:accountCommentIndex' element={<PendingPost />} />
|
<Route path='/profile/:accountCommentIndex' element={<PendingPost />} />
|
||||||
<Route path='/profile/:accountCommentIndex/settings' element={<PendingPost />} />
|
<Route path='/profile/:accountCommentIndex/settings' element={<PendingPost />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ const UpdateButton = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const handleUpdate = () => {
|
const handleManualUpdate = () => {
|
||||||
window.alert('Manual updates are not available yet. Posts update automatically every ~2 minutes.');
|
window.alert('Manual updates are not available yet. Posts update automatically every ~2 minutes.');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,11 +100,11 @@ const UpdateButton = () => {
|
|||||||
<>
|
<>
|
||||||
{/* TODO: Implement update button once available in API */}
|
{/* TODO: Implement update button once available in API */}
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<button className={`button ${styles.disabledButton}`} onClick={handleUpdate}>
|
<button className={`button ${styles.disabledButton}`} onClick={handleManualUpdate}>
|
||||||
{t('update')}
|
{t('update')}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button className={`button ${styles.disabledButton}`} onClick={handleUpdate}>
|
<button className={`button ${styles.disabledButton}`} onClick={handleManualUpdate}>
|
||||||
{t('update')}
|
{t('update')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -116,17 +116,21 @@ const AutoButton = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const handleManualUpdate = () => {
|
||||||
|
window.alert('Manual updates are not available yet. Posts update automatically every ~2 minutes.');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<button className='button'>
|
<button className='button' onClick={handleManualUpdate}>
|
||||||
<label>
|
<label>
|
||||||
<input type='checkbox' className={styles.autoCheckbox} checked disabled />
|
<input type='checkbox' className={styles.autoCheckbox} checked disabled />
|
||||||
{t('Auto')}
|
{t('Auto')}
|
||||||
</label>
|
</label>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<label>
|
<label onClick={handleManualUpdate}>
|
||||||
{' '}
|
{' '}
|
||||||
<input type='checkbox' className={styles.autoCheckbox} checked disabled /> {t('Auto')}
|
<input type='checkbox' className={styles.autoCheckbox} checked disabled /> {t('Auto')}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
border-image-width: 40px 0px 0px 0px;
|
border-image-width: 40px 0px 0px 0px;
|
||||||
border-image-outset: 0px 0px 0px 0px;
|
border-image-outset: 0px 0px 0px 0px;
|
||||||
border-image-repeat: repeat repeat;
|
border-image-repeat: repeat repeat;
|
||||||
border-image-source: url('/public/assets/garland.png');
|
border-image-source: url('/assets/garland.png');
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { useAccountComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
|
||||||
import styles from './board-header.module.css';
|
import styles from './board-header.module.css';
|
||||||
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
|
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
|
||||||
import useIsMobile from '../../hooks/use-is-mobile';
|
import useIsMobile from '../../hooks/use-is-mobile';
|
||||||
@@ -25,7 +25,7 @@ const BoardHeader = () => {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
||||||
|
const isInModView = isModView(location.pathname);
|
||||||
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
||||||
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
|
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
|
||||||
|
|
||||||
@@ -33,8 +33,8 @@ const BoardHeader = () => {
|
|||||||
const { address, shortAddress } = subplebbit || {};
|
const { address, shortAddress } = subplebbit || {};
|
||||||
|
|
||||||
const multisubMetadata = useMultisubMetadata();
|
const multisubMetadata = useMultisubMetadata();
|
||||||
const title = isInAllView ? multisubMetadata?.title || 'all' : isInSubscriptionsView ? 'Subscriptions' : subplebbit?.title;
|
const title = isInAllView ? multisubMetadata?.title || 'all' : isInSubscriptionsView ? 'Subscriptions' : isInModView ? 'Mod' : subplebbit?.title;
|
||||||
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : `p/${address}`;
|
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : isInModView ? 'p/mod' : `p/${address}`;
|
||||||
|
|
||||||
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ const BoardHeader = () => {
|
|||||||
)}
|
)}
|
||||||
<div className={styles.boardTitle}>
|
<div className={styles.boardTitle}>
|
||||||
{title || (shortAddress ? (shortAddress.endsWith('.eth') || shortAddress.endsWith('.sol') ? shortAddress.slice(0, -4) : shortAddress) : subplebbitAddress)}
|
{title || (shortAddress ? (shortAddress.endsWith('.eth') || shortAddress.endsWith('.sol') ? shortAddress.slice(0, -4) : shortAddress) : subplebbitAddress)}
|
||||||
{(isOffline || isOnlineStatusLoading) && !isInAllView && !isInSubscriptionsView && (
|
{(isOffline || isOnlineStatusLoading) && !isInAllView && !isInSubscriptionsView && !isInModView && (
|
||||||
<span className={styles.offlineIconWrapper}>
|
<span className={styles.offlineIconWrapper}>
|
||||||
<Tooltip content={offlineTitle}>
|
<Tooltip content={offlineTitle}>
|
||||||
<span className={`${styles.offlineIcon} ${offlineIconClass}`} />
|
<span className={`${styles.offlineIcon} ${offlineIconClass}`} />
|
||||||
|
|||||||
@@ -58,11 +58,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.threadIcons .stickyIcon {
|
.threadIcons .stickyIcon {
|
||||||
background-image: url("/public/assets/icons/sticky.gif");
|
background-image: url("/assets/icons/sticky.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
.threadIcons .closedIcon {
|
.threadIcons .closedIcon {
|
||||||
background-image: url("/public/assets/icons/closed.gif");
|
background-image: url("/assets/icons/closed.gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
.mediaWrapper {
|
.mediaWrapper {
|
||||||
@@ -72,6 +72,7 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
clip-path: inset(-5px); /* direct media could change its dimensions after being published, so we need to clip the overflow. We can't use overflow: hidden because it will hide the box-shadow */
|
||||||
}
|
}
|
||||||
|
|
||||||
.post img, .post video, .post audio {
|
.post img, .post video, .post audio {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
|
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||||
import { useFloating, offset, size, autoUpdate, Placement } from '@floating-ui/react';
|
import { useFloating, offset, size, autoUpdate, Placement } from '@floating-ui/react';
|
||||||
import { getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||||
@@ -20,6 +20,7 @@ import _ from 'lodash';
|
|||||||
import { ContentPreview } from '../../views/home/popular-threads-box';
|
import { ContentPreview } from '../../views/home/popular-threads-box';
|
||||||
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
|
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
|
||||||
import { shouldShowSnow } from '../../lib/snow';
|
import { shouldShowSnow } from '../../lib/snow';
|
||||||
|
import useReplies from '../../hooks/use-replies';
|
||||||
|
|
||||||
interface CatalogPostMediaProps {
|
interface CatalogPostMediaProps {
|
||||||
commentMediaInfo: any;
|
commentMediaInfo: any;
|
||||||
@@ -104,7 +105,6 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
content,
|
content,
|
||||||
isDescription,
|
isDescription,
|
||||||
isRules,
|
isRules,
|
||||||
lastChildCid,
|
|
||||||
link,
|
link,
|
||||||
linkHeight,
|
linkHeight,
|
||||||
linkWidth,
|
linkWidth,
|
||||||
@@ -115,10 +115,11 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
subplebbitAddress,
|
subplebbitAddress,
|
||||||
timestamp,
|
timestamp,
|
||||||
title,
|
title,
|
||||||
|
thumbnailUrl,
|
||||||
} = post || {};
|
} = post || {};
|
||||||
const linkCount = useCountLinksInReplies(post);
|
const linkCount = useCountLinksInReplies(post);
|
||||||
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(post);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
const { hidden } = useHide({ cid });
|
const { hidden } = useHide({ cid });
|
||||||
@@ -181,7 +182,8 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
update();
|
update();
|
||||||
}, [update, windowWidth]);
|
}, [update, windowWidth]);
|
||||||
|
|
||||||
const lastReply = useComment({ commentCid: lastChildCid });
|
const replies = useReplies(post);
|
||||||
|
const lastReply = replies.length > 0 ? replies[replies.length - 1] : null;
|
||||||
|
|
||||||
const { isCommentAuthorMod: isCatalogPostAuthorMod, commentAuthorRole: catalogPostAuthorRole } = useEditCommentPrivileges({
|
const { isCommentAuthorMod: isCatalogPostAuthorMod, commentAuthorRole: catalogPostAuthorRole } = useEditCommentPrivileges({
|
||||||
commentAuthorAddress: author?.address,
|
commentAuthorAddress: author?.address,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
.container {
|
.container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: calc(50% - 150px);
|
|
||||||
left: calc(50% - 150px);
|
|
||||||
display: block;
|
display: block;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
@@ -10,6 +8,9 @@
|
|||||||
z-index: 999;
|
z-index: 999;
|
||||||
background-color: var(--challenge-modal-background-color);
|
background-color: var(--challenge-modal-background-color);
|
||||||
border: var(--challenge-modal-border);
|
border: var(--challenge-modal-border);
|
||||||
|
will-change: transform;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@@ -24,6 +25,9 @@
|
|||||||
background-color: var(--challenge-modal-title-background-color);
|
background-color: var(--challenge-modal-title-background-color);
|
||||||
color: var(--challenge-modal-title-text-color);
|
color: var(--challenge-modal-title-text-color);
|
||||||
border: var(--challenge-modal-title-border);
|
border: var(--challenge-modal-title-border);
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeIcon {
|
.closeIcon {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useRef, useState, useEffect } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
import Draggable from 'react-draggable';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Challenge as ChallengeType } from '@plebbit/plebbit-react-hooks';
|
import { Challenge as ChallengeType } from '@plebbit/plebbit-react-hooks';
|
||||||
import { getPublicationType } from '../../lib/utils/challenge-utils';
|
import { getPublicationType } from '../../lib/utils/challenge-utils';
|
||||||
@@ -7,6 +6,8 @@ import useIsMobile from '../../hooks/use-is-mobile';
|
|||||||
import useChallengesStore from '../../stores/use-challenges-store';
|
import useChallengesStore from '../../stores/use-challenges-store';
|
||||||
import styles from './challenge-modal.module.css';
|
import styles from './challenge-modal.module.css';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { useSpring, animated } from '@react-spring/web';
|
||||||
|
import { useDrag } from '@use-gesture/react';
|
||||||
|
|
||||||
interface ChallengeProps {
|
interface ChallengeProps {
|
||||||
challenge: ChallengeType;
|
challenge: ChallengeType;
|
||||||
@@ -63,13 +64,44 @@ const Challenge = ({ challenge, closeModal }: ChallengeProps) => {
|
|||||||
return () => document.removeEventListener('keydown', onEscapeKey);
|
return () => document.removeEventListener('keydown', onEscapeKey);
|
||||||
}, [closeModal]);
|
}, [closeModal]);
|
||||||
|
|
||||||
// react-draggable requires a ref to the modal node
|
const nodeRef = useRef<HTMLDivElement>(null);
|
||||||
const nodeRef = useRef(null);
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const [{ x, y }, api] = useSpring(() => ({
|
||||||
|
x: window.innerWidth / 2 - 150,
|
||||||
|
y: window.innerHeight / 2 - 200,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const bind = useDrag(
|
||||||
|
({ active, event, offset: [ox, oy] }) => {
|
||||||
|
if (active) {
|
||||||
|
event.preventDefault();
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
document.body.style.webkitUserSelect = 'none';
|
||||||
|
} else {
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
document.body.style.webkitUserSelect = '';
|
||||||
|
}
|
||||||
|
api.start({ x: ox, y: oy, immediate: true });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: () => [x.get(), y.get()],
|
||||||
|
filterTaps: true,
|
||||||
|
bounds: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const modalContent = (
|
const modalContent = (
|
||||||
<div className={styles.container} ref={nodeRef}>
|
<animated.div
|
||||||
<div className={`challengeHandle ${styles.title}`}>
|
className={styles.container}
|
||||||
|
ref={nodeRef}
|
||||||
|
style={{
|
||||||
|
x: isMobile ? 0 : x,
|
||||||
|
y: isMobile ? 0 : y,
|
||||||
|
touchAction: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={`challengeHandle ${styles.title}`} {...(!isMobile ? bind() : {})}>
|
||||||
Challenge for {publicationType}
|
Challenge for {publicationType}
|
||||||
<button className={styles.closeIcon} onClick={closeModal} title='close' />
|
<button className={styles.closeIcon} onClick={closeModal} title='close' />
|
||||||
</div>
|
</div>
|
||||||
@@ -127,16 +159,10 @@ const Challenge = ({ challenge, closeModal }: ChallengeProps) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</animated.div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return isMobile ? (
|
return modalContent;
|
||||||
modalContent
|
|
||||||
) : (
|
|
||||||
<Draggable handle='.challengeHandle' nodeRef={nodeRef}>
|
|
||||||
{modalContent}
|
|
||||||
</Draggable>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChallengeModal = () => {
|
const ChallengeModal = () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||||
import { isPostPageView } from '../../lib/utils/view-utils';
|
import { isPostPageView } from '../../lib/utils/view-utils';
|
||||||
import useIsMobile from '../../hooks/use-is-mobile';
|
import useIsMobile from '../../hooks/use-is-mobile';
|
||||||
@@ -9,11 +10,10 @@ import LoadingEllipsis from '../../components/loading-ellipsis';
|
|||||||
import ReplyQuotePreview from '../../components/reply-quote-preview';
|
import ReplyQuotePreview from '../../components/reply-quote-preview';
|
||||||
import Markdown from '../../components/markdown';
|
import Markdown from '../../components/markdown';
|
||||||
import Tooltip from '../../components/tooltip';
|
import Tooltip from '../../components/tooltip';
|
||||||
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
|
|
||||||
import styles from '../../views/post/post.module.css';
|
import styles from '../../views/post/post.module.css';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
const CommentContent = ({ comment: post, replies }: { comment: Comment; replies: Comment[] }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -34,9 +34,10 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
? content.slice(0, 2000)
|
? content.slice(0, 2000)
|
||||||
: content);
|
: content);
|
||||||
|
|
||||||
const quotelinkReply = useComment({ commentCid: parentCid });
|
const quotelinkReply = parentCid && replies?.find((reply) => reply.cid === parentCid);
|
||||||
const isReply = parentCid;
|
|
||||||
const isReplyingToReply = (postCid && postCid !== parentCid) || quotelinkReply?.postCid !== parentCid;
|
const isReply = !!parentCid;
|
||||||
|
const isReplyingToReply = isReply && parentCid !== postCid;
|
||||||
|
|
||||||
const stateString = useStateString(post);
|
const stateString = useStateString(post);
|
||||||
|
|
||||||
@@ -44,7 +45,9 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<blockquote className={`${styles.postMessage} ${!isReply && isMobile && styles.clampLines} ${isRules && styles.rulesMessage}`}>
|
<blockquote className={`${styles.postMessage} ${!isReply && isMobile && styles.clampLines} ${isRules && styles.rulesMessage}`}>
|
||||||
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
|
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && (
|
||||||
|
<ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} replies={replies} />
|
||||||
|
)}
|
||||||
{removed ? (
|
{removed ? (
|
||||||
reason ? (
|
reason ? (
|
||||||
<>
|
<>
|
||||||
@@ -84,7 +87,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
i18nKey={'comment_edited_at_timestamp'}
|
i18nKey={'comment_edited_at_timestamp'}
|
||||||
values={{ timestamp: getFormattedDate(edit?.timestamp) }}
|
values={{ timestamp: getFormattedDate(edit?.timestamp) }}
|
||||||
shouldUnescape={true}
|
shouldUnescape={true}
|
||||||
components={{ 1: <Tooltip content={getFormattedTimeAgo(edit?.timestamp)} children={<></>} /> }}
|
components={{ 1: <Tooltip content={getFormattedTimeAgo(edit?.timestamp)} children={<Fragment key={edit?.timestamp}></Fragment>} /> }}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
|
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
|
||||||
{showOriginal ? (
|
{showOriginal ? (
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
|
||||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||||
import { getHostname } from '../../lib/utils/url-utils';
|
import { getHostname } from '../../lib/utils/url-utils';
|
||||||
import useExpandedMediaStore from '../../stores/use-expanded-media-store';
|
import useExpandedMediaStore from '../../stores/use-expanded-media-store';
|
||||||
@@ -14,12 +13,14 @@ interface MediaProps {
|
|||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
displayHeight?: string;
|
displayHeight?: string;
|
||||||
displayWidth?: string;
|
displayWidth?: string;
|
||||||
|
isDescription?: boolean;
|
||||||
|
isRules?: boolean;
|
||||||
isFloatingEmbed?: boolean;
|
isFloatingEmbed?: boolean;
|
||||||
isOutOfFeed?: boolean;
|
isOutOfFeed?: boolean;
|
||||||
isReply?: boolean;
|
isReply?: boolean;
|
||||||
linkHeight?: number;
|
linkHeight?: number;
|
||||||
linkWidth?: number;
|
linkWidth?: number;
|
||||||
post?: Comment;
|
parentCid?: string;
|
||||||
removed?: boolean;
|
removed?: boolean;
|
||||||
spoiler?: boolean;
|
spoiler?: boolean;
|
||||||
showThumbnail?: boolean;
|
showThumbnail?: boolean;
|
||||||
@@ -130,12 +131,12 @@ interface ImageProps {
|
|||||||
displayHeight: string;
|
displayHeight: string;
|
||||||
displayWidth: string;
|
displayWidth: string;
|
||||||
isOutOfFeed: boolean;
|
isOutOfFeed: boolean;
|
||||||
post: Comment | undefined;
|
parentCid?: string;
|
||||||
|
spoiler?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, post }: ImageProps) => {
|
const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, parentCid, spoiler }: ImageProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { parentCid, spoiler } = post || {};
|
|
||||||
const { type, url } = commentMediaInfo || {};
|
const { type, url } = commentMediaInfo || {};
|
||||||
const isReply = parentCid;
|
const isReply = parentCid;
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
@@ -181,7 +182,7 @@ const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, pos
|
|||||||
{mediaDimensions && `, ${mediaDimensions}`})
|
{mediaDimensions && `, ${mediaDimensions}`})
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{type && !isImageExpanded && <div className={styles.fileInfo}>{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
{type && !isImageExpanded && <div className={styles.fileInfo}>{`${spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
@@ -197,8 +198,20 @@ const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, pos
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail, setShowThumbnail }: MediaProps) => {
|
const CommentMedia = ({
|
||||||
const { deleted, linkHeight, linkWidth, parentCid, removed, spoiler } = post || {};
|
commentMediaInfo,
|
||||||
|
deleted,
|
||||||
|
isDescription,
|
||||||
|
isFloatingEmbed,
|
||||||
|
isRules,
|
||||||
|
linkHeight,
|
||||||
|
linkWidth,
|
||||||
|
parentCid,
|
||||||
|
removed,
|
||||||
|
showThumbnail,
|
||||||
|
setShowThumbnail,
|
||||||
|
spoiler,
|
||||||
|
}: MediaProps) => {
|
||||||
const isReply = parentCid;
|
const isReply = parentCid;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
@@ -235,14 +248,20 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail,
|
|||||||
displayWidth = '100%';
|
displayWidth = '100%';
|
||||||
displayHeight = '100%';
|
displayHeight = '100%';
|
||||||
}
|
}
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
const isOutOfFeed = isDescription || isRules || isFloatingEmbed || spoiler || false; // virtuoso wrapper unneeded
|
||||||
const isOutOfFeed = isDescription || isRules || isFloatingEmbed || spoiler; // virtuoso wrapper unneeded
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.content}>
|
<span className={styles.content}>
|
||||||
{commentMediaInfo?.type === 'image' ? (
|
{commentMediaInfo?.type === 'image' ? (
|
||||||
// images just enlarge when clicked, so they don't need two separate components
|
// images just enlarge when clicked, so they don't need two separate components
|
||||||
<Image commentMediaInfo={commentMediaInfo} displayHeight={displayHeight} displayWidth={displayWidth} isOutOfFeed={isOutOfFeed} post={post} />
|
<Image
|
||||||
|
commentMediaInfo={commentMediaInfo}
|
||||||
|
displayHeight={displayHeight}
|
||||||
|
displayWidth={displayWidth}
|
||||||
|
isOutOfFeed={isOutOfFeed}
|
||||||
|
parentCid={parentCid}
|
||||||
|
spoiler={spoiler}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`}>
|
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`}>
|
||||||
@@ -259,9 +278,9 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail,
|
|||||||
setShowThumbnail={setShowThumbnail}
|
setShowThumbnail={setShowThumbnail}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isMobile && type && <div className={styles.fileInfo}>{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
{isMobile && type && <div className={styles.fileInfo}>{`${spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||||
</span>
|
</span>
|
||||||
{!showThumbnail && <Media commentMediaInfo={commentMediaInfo} isReply={post?.parentCid} setShowThumbnail={setShowThumbnail} />}
|
{!showThumbnail && <Media commentMediaInfo={commentMediaInfo} isReply={!!parentCid} setShowThumbnail={setShowThumbnail} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment, useAuthorAvatar, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAuthorAvatar, useEditedComment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||||
import styles from '../../views/post/post.module.css';
|
import styles from '../../views/post/post.module.css';
|
||||||
import { getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||||
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
|
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
|
||||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||||
import { isValidURL } from '../../lib/utils/url-utils';
|
import { isValidURL } from '../../lib/utils/url-utils';
|
||||||
@@ -29,6 +30,7 @@ import { PostProps } from '../../views/post/post';
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { shouldShowSnow } from '../../lib/snow';
|
import { shouldShowSnow } from '../../lib/snow';
|
||||||
|
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||||
|
|
||||||
interface ShowOmittedRepliesState {
|
interface ShowOmittedRepliesState {
|
||||||
showOmittedReplies: Record<string, boolean>;
|
showOmittedReplies: Record<string, boolean>;
|
||||||
@@ -46,7 +48,7 @@ const useShowOmittedReplies = create<ShowOmittedRepliesState>((set) => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }: PostProps) => {
|
const PostInfo = ({ post, postReplyCount = 0, roles, isHidden, replies: threadReplies }: PostProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, shortCid, state, subplebbitAddress, timestamp } = post || {};
|
const { author, cid, deleted, locked, pinned, parentCid, postCid, reason, removed, shortCid, state, subplebbitAddress, timestamp } = post || {};
|
||||||
const title = post?.title?.trim();
|
const title = post?.title?.trim();
|
||||||
@@ -76,6 +78,8 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
|
|||||||
|
|
||||||
const { hidden } = useHide(post);
|
const { hidden } = useHide(post);
|
||||||
|
|
||||||
|
const { openReplyModal } = useReplyModalStore();
|
||||||
|
|
||||||
const onReplyModalClick = () => {
|
const onReplyModalClick = () => {
|
||||||
deleted
|
deleted
|
||||||
? isReply
|
? isReply
|
||||||
@@ -215,24 +219,28 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
|
|||||||
(reply: Comment, index: number) =>
|
(reply: Comment, index: number) =>
|
||||||
reply?.parentCid === cid &&
|
reply?.parentCid === cid &&
|
||||||
reply?.cid &&
|
reply?.cid &&
|
||||||
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={index} isBacklinkReply={true} backlinkReply={reply} />,
|
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={index} isBacklinkReply={true} backlinkReply={reply} replies={threadReplies} />,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostMedia = ({ post, hasThumbnail }: PostProps) => {
|
interface PostMediaProps {
|
||||||
|
commentMediaInfo: CommentMediaInfo | undefined;
|
||||||
|
hasThumbnail: boolean;
|
||||||
|
isDescription: boolean;
|
||||||
|
isRules: boolean;
|
||||||
|
spoiler: boolean;
|
||||||
|
deleted: boolean;
|
||||||
|
removed: boolean;
|
||||||
|
linkHeight: number;
|
||||||
|
linkWidth: number;
|
||||||
|
parentCid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostMedia = ({ commentMediaInfo, hasThumbnail, isDescription, isRules, spoiler, deleted, removed, linkHeight, linkWidth, parentCid }: PostMediaProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { spoiler, cid } = post || {};
|
|
||||||
|
|
||||||
// Reset state by remounting component when post changes
|
|
||||||
return <PostMediaContent key={cid} post={post} hasThumbnail={hasThumbnail} spoiler={spoiler} t={t} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasThumbnail: boolean | undefined; spoiler: boolean; t: any }) => {
|
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(post);
|
|
||||||
const { url } = commentMediaInfo || {};
|
const { url } = commentMediaInfo || {};
|
||||||
let type = commentMediaInfo?.type;
|
let type = commentMediaInfo?.type;
|
||||||
const gifFrameUrl = useFetchGifFirstFrame(url);
|
const gifFrameUrl = useFetchGifFirstFrame(url);
|
||||||
@@ -268,8 +276,7 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
|
|||||||
)}
|
)}
|
||||||
{showThumbnail && !hasThumbnail && embedUrl && canEmbed(embedUrl) && (
|
{showThumbnail && !hasThumbnail && embedUrl && canEmbed(embedUrl) && (
|
||||||
<span>
|
<span>
|
||||||
{' '}
|
-[
|
||||||
[
|
|
||||||
<span className={styles.closeMedia} onClick={() => setShowThumbnail(false)}>
|
<span className={styles.closeMedia} onClick={() => setShowThumbnail(false)}>
|
||||||
{t('open')}
|
{t('open')}
|
||||||
</span>
|
</span>
|
||||||
@@ -281,10 +288,17 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
|
|||||||
<div className={styles.fileThumbnail}>
|
<div className={styles.fileThumbnail}>
|
||||||
<CommentMedia
|
<CommentMedia
|
||||||
commentMediaInfo={commentMediaInfo}
|
commentMediaInfo={commentMediaInfo}
|
||||||
post={post}
|
deleted={deleted}
|
||||||
|
isDescription={isDescription}
|
||||||
|
isRules={isRules}
|
||||||
|
removed={removed}
|
||||||
|
linkHeight={linkHeight}
|
||||||
|
linkWidth={linkWidth}
|
||||||
showThumbnail={showThumbnail}
|
showThumbnail={showThumbnail}
|
||||||
setShowThumbnail={setShowThumbnail}
|
setShowThumbnail={setShowThumbnail}
|
||||||
isOutOfFeed={isDescription || isRules}
|
isOutOfFeed={isDescription || isRules}
|
||||||
|
parentCid={parentCid}
|
||||||
|
spoiler={spoiler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -292,7 +306,7 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
const Reply = ({ postReplyCount, reply, roles, replies }: PostProps) => {
|
||||||
let post = reply;
|
let post = reply;
|
||||||
// handle pending mod or author edit
|
// handle pending mod or author edit
|
||||||
const { editedComment } = useEditedComment({ comment: reply });
|
const { editedComment } = useEditedComment({ comment: reply });
|
||||||
@@ -300,28 +314,43 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
|||||||
post = editedComment;
|
post = editedComment;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { author, cid, deleted, link, postCid, reason, removed, subplebbitAddress } = post || {};
|
const { author, cid, deleted, link, linkHeight, linkWidth, postCid, reason, removed, spoiler, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
|
||||||
|
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||||
|
|
||||||
const isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`);
|
const isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`);
|
||||||
const { hidden } = useHide({ cid });
|
const { hidden } = useHide({ cid });
|
||||||
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(post);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.replyDesktop}>
|
<div className={styles.replyDesktop}>
|
||||||
<div className={styles.sideArrows}>{'>>'}</div>
|
<div className={styles.sideArrows}>{'>>'}</div>
|
||||||
<div className={`${styles.reply} ${isRouteLinkToReply && styles.highlight}`} data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid}>
|
<div className={`${styles.reply} ${isRouteLinkToReply && styles.highlight}`} data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid}>
|
||||||
<PostInfo openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} isHidden={hidden} />
|
<PostInfo post={post} postReplyCount={postReplyCount} roles={roles} isHidden={hidden} replies={replies} />
|
||||||
{link && !hidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} hasThumbnail={hasThumbnail} />}
|
{link && !hidden && !(deleted || removed) && isValidURL(link) && (
|
||||||
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} />}
|
<PostMedia
|
||||||
|
commentMediaInfo={commentMediaInfo}
|
||||||
|
hasThumbnail={hasThumbnail}
|
||||||
|
isDescription={isDescription}
|
||||||
|
isRules={isRules}
|
||||||
|
spoiler={spoiler}
|
||||||
|
deleted={deleted}
|
||||||
|
removed={removed}
|
||||||
|
linkHeight={linkHeight}
|
||||||
|
linkWidth={linkWidth}
|
||||||
|
parentCid={parentCid}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} replies={replies || []} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
const PostDesktop = ({ post, roles, showAllReplies, showReplies = true, replies: threadReplies }: PostProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, content, deleted, link, pinned, postCid, removed, state, subplebbitAddress } = post || {};
|
const { author, cid, content, deleted, link, linkHeight, linkWidth, pinned, postCid, removed, spoiler, state, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -331,7 +360,8 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
|||||||
const { hidden, unhide, hide } = useHide({ cid });
|
const { hidden, unhide, hide } = useHide({ cid });
|
||||||
const isHidden = hidden && !isInPostPageView;
|
const isHidden = hidden && !isInPostPageView;
|
||||||
|
|
||||||
const replies = useReplies(post);
|
const commentReplies = useReplies(post);
|
||||||
|
const replies = threadReplies || commentReplies;
|
||||||
const visiblelinksCount = useCountLinksInReplies(post, 5);
|
const visiblelinksCount = useCountLinksInReplies(post, 5);
|
||||||
const totalLinksCount = useCountLinksInReplies(post);
|
const totalLinksCount = useCountLinksInReplies(post);
|
||||||
const replyCount = replies?.length;
|
const replyCount = replies?.length;
|
||||||
@@ -342,7 +372,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
|||||||
|
|
||||||
const stateString = useStateString(post) || t('loading_board');
|
const stateString = useStateString(post) || t('loading_board');
|
||||||
|
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
||||||
|
|
||||||
const subplebbitRulesReply = {
|
const subplebbitRulesReply = {
|
||||||
isRules: true,
|
isRules: true,
|
||||||
@@ -353,7 +383,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
|||||||
replyCount: 0,
|
replyCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(post);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -373,10 +403,23 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
|||||||
)}
|
)}
|
||||||
<div data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid} className={shouldShowSnow() && hasThumbnail ? styles.xmasHatWrapper : ''}>
|
<div data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid} className={shouldShowSnow() && hasThumbnail ? styles.xmasHatWrapper : ''}>
|
||||||
{shouldShowSnow() && hasThumbnail && <img src={`${process.env.PUBLIC_URL}/assets/xmashat.gif`} className={styles.xmasHat} alt='' />}
|
{shouldShowSnow() && hasThumbnail && <img src={`${process.env.PUBLIC_URL}/assets/xmashat.gif`} className={styles.xmasHat} alt='' />}
|
||||||
{link && !isHidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} hasThumbnail={hasThumbnail} />}
|
{link && !isHidden && !(deleted || removed) && isValidURL(link) && (
|
||||||
<PostInfo isHidden={hidden} openReplyModal={openReplyModal} post={post} postReplyCount={replyCount} roles={roles} />
|
<PostMedia
|
||||||
|
commentMediaInfo={commentMediaInfo}
|
||||||
|
hasThumbnail={hasThumbnail}
|
||||||
|
isDescription={isDescription}
|
||||||
|
isRules={isRules}
|
||||||
|
spoiler={spoiler}
|
||||||
|
deleted={deleted}
|
||||||
|
removed={removed}
|
||||||
|
linkHeight={linkHeight}
|
||||||
|
linkWidth={linkWidth}
|
||||||
|
parentCid={parentCid}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<PostInfo isHidden={hidden} post={post} postReplyCount={replyCount} roles={roles} replies={replies} />
|
||||||
{!isHidden && !content && !(deleted || removed) && <div className={styles.spacer} />}
|
{!isHidden && !content && !(deleted || removed) && <div className={styles.spacer} />}
|
||||||
{!isHidden && <CommentContent comment={post} />}
|
{!isHidden && <CommentContent comment={post} replies={replies} />}
|
||||||
</div>
|
</div>
|
||||||
{!isHidden && !isDescription && !isRules && !isInPendingPostView && (replyCount > 5 || (pinned && repliesCount > 0)) && !isInPostPageView && (
|
{!isHidden && !isDescription && !isRules && !isInPendingPostView && (replyCount > 5 || (pinned && repliesCount > 0)) && !isInPostPageView && (
|
||||||
<span className={styles.summary}>
|
<span className={styles.summary}>
|
||||||
@@ -405,7 +448,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
|||||||
showReplies &&
|
showReplies &&
|
||||||
(showAllReplies || showOmittedReplies[cid] ? replies : replies.slice(-5)).map((reply, index) => (
|
(showAllReplies || showOmittedReplies[cid] ? replies : replies.slice(-5)).map((reply, index) => (
|
||||||
<div key={index} className={styles.replyContainer}>
|
<div key={index} className={styles.replyContainer}>
|
||||||
<Reply openReplyModal={openReplyModal} reply={reply} roles={roles} postReplyCount={replyCount} />
|
<Reply reply={reply} roles={roles} postReplyCount={replyCount} replies={replies} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{isDescription && subplebbit?.rules && subplebbit?.rules.length > 0 && (
|
{isDescription && subplebbit?.rules && subplebbit?.rules.length > 0 && (
|
||||||
|
|||||||
@@ -132,8 +132,8 @@ const BlockBoardButton = ({ address }: { address: string }) => {
|
|||||||
|
|
||||||
const PostMenuDesktop = ({ post }: { post: Comment }) => {
|
const PostMenuDesktop = ({ post }: { post: Comment }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, isDescription, isRules, link, postCid, subplebbitAddress } = post || {};
|
const { author, cid, isDescription, isRules, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = post || {};
|
||||||
const commentMediaInfo = getCommentMediaInfo(post);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||||
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -122,9 +122,9 @@ const BlockBoardButton = ({ address }: { address: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PostMenuMobile = ({ post }: { post: Comment }) => {
|
const PostMenuMobile = ({ post }: { post: Comment }) => {
|
||||||
const { author, cid, deleted, isDescription, isRules, link, parentCid, postCid, removed, subplebbitAddress } = post || {};
|
const { author, cid, deleted, isDescription, isRules, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } = post || {};
|
||||||
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: author?.address, subplebbitAddress });
|
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: author?.address, subplebbitAddress });
|
||||||
const commentMediaInfo = getCommentMediaInfo(post);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const { refs, floatingStyles, context } = useFloating({
|
const { refs, floatingStyles, context } = useFloating({
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment, useAuthorAvatar, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAuthorAvatar, useEditedComment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||||
import styles from '../../views/post/post.module.css';
|
import styles from '../../views/post/post.module.css';
|
||||||
import { shouldShowSnow } from '../../lib/snow';
|
import { shouldShowSnow } from '../../lib/snow';
|
||||||
@@ -24,10 +25,29 @@ import ReplyQuotePreview from '../reply-quote-preview';
|
|||||||
import Tooltip from '../tooltip';
|
import Tooltip from '../tooltip';
|
||||||
import { PostProps } from '../../views/post/post';
|
import { PostProps } from '../../views/post/post';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||||
|
|
||||||
const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: PostProps) => {
|
const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, deleted, link, locked, parentCid, pinned, postCid, reason, removed, shortCid, state, subplebbitAddress, timestamp } = post || {};
|
const {
|
||||||
|
author,
|
||||||
|
cid,
|
||||||
|
deleted,
|
||||||
|
link,
|
||||||
|
linkHeight,
|
||||||
|
linkWidth,
|
||||||
|
locked,
|
||||||
|
parentCid,
|
||||||
|
pinned,
|
||||||
|
postCid,
|
||||||
|
reason,
|
||||||
|
removed,
|
||||||
|
shortCid,
|
||||||
|
state,
|
||||||
|
subplebbitAddress,
|
||||||
|
timestamp,
|
||||||
|
thumbnailUrl,
|
||||||
|
} = post || {};
|
||||||
const isReply = parentCid;
|
const isReply = parentCid;
|
||||||
const title = post?.title?.trim();
|
const title = post?.title?.trim();
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||||
@@ -43,7 +63,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
|||||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||||
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(post);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
const stateString = useStateString(post);
|
const stateString = useStateString(post);
|
||||||
@@ -57,6 +77,8 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
|||||||
|
|
||||||
const { hidden } = useHide(post);
|
const { hidden } = useHide(post);
|
||||||
|
|
||||||
|
const { openReplyModal } = useReplyModalStore();
|
||||||
|
|
||||||
const onReplyModalClick = () => {
|
const onReplyModalClick = () => {
|
||||||
deleted
|
deleted
|
||||||
? isReply
|
? isReply
|
||||||
@@ -179,31 +201,39 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{(hasThumbnail || link) && !(deleted || removed) && <PostMediaContent key={cid} post={post} link={link} t={t} />}
|
{(hasThumbnail || link) && !(deleted || removed) && <PostMediaContent key={cid} post={post} link={link} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostMediaContent = ({ post, link, t }: { post: any; link: string; t: any }) => {
|
const PostMediaContent = ({ post, link }: { post: any; link: string }) => {
|
||||||
const [showThumbnail, setShowThumbnail] = useState(true);
|
const [showThumbnail, setShowThumbnail] = useState(true);
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||||
const commentMediaInfo = useCommentMediaInfo(post);
|
const { thumbnailUrl, linkWidth, linkHeight, spoiler, deleted, removed, parentCid } = post || {};
|
||||||
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
hasThumbnail && (
|
hasThumbnail && (
|
||||||
<CommentMedia
|
<CommentMedia
|
||||||
commentMediaInfo={commentMediaInfo}
|
commentMediaInfo={commentMediaInfo}
|
||||||
post={post}
|
deleted={deleted}
|
||||||
|
isDescription={isDescription}
|
||||||
|
isRules={isRules}
|
||||||
|
removed={removed}
|
||||||
|
linkHeight={linkHeight}
|
||||||
|
linkWidth={linkWidth}
|
||||||
showThumbnail={showThumbnail}
|
showThumbnail={showThumbnail}
|
||||||
setShowThumbnail={setShowThumbnail}
|
setShowThumbnail={setShowThumbnail}
|
||||||
isOutOfFeed={isDescription || isRules}
|
isOutOfFeed={isDescription || isRules}
|
||||||
|
parentCid={parentCid}
|
||||||
|
spoiler={spoiler}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReplyBacklinks = ({ post }: PostProps) => {
|
const ReplyBacklinks = ({ post, replies: threadReplies }: PostProps) => {
|
||||||
const { cid, parentCid } = post || {};
|
const { cid, parentCid } = post || {};
|
||||||
const replies = useReplies(post);
|
const replies = useReplies(post);
|
||||||
|
|
||||||
@@ -216,14 +246,14 @@ const ReplyBacklinks = ({ post }: PostProps) => {
|
|||||||
(reply: Comment, index: number) =>
|
(reply: Comment, index: number) =>
|
||||||
reply?.parentCid === cid &&
|
reply?.parentCid === cid &&
|
||||||
reply?.cid &&
|
reply?.cid &&
|
||||||
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={index} isBacklinkReply={true} backlinkReply={reply} />,
|
!(reply?.deleted || reply?.removed) && <ReplyQuotePreview key={index} isBacklinkReply={true} backlinkReply={reply} replies={threadReplies} />,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
const Reply = ({ postReplyCount, reply, replies, roles }: PostProps) => {
|
||||||
let post = reply;
|
let post = reply;
|
||||||
// handle pending mod or author edit
|
// handle pending mod or author edit
|
||||||
const { editedComment } = useEditedComment({ comment: reply });
|
const { editedComment } = useEditedComment({ comment: reply });
|
||||||
@@ -243,8 +273,8 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
|||||||
data-author-address={author?.shortAddress}
|
data-author-address={author?.shortAddress}
|
||||||
data-post-cid={postCid}
|
data-post-cid={postCid}
|
||||||
>
|
>
|
||||||
<PostInfoAndMedia openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} />
|
<PostInfoAndMedia post={post} postReplyCount={postReplyCount} roles={roles} />
|
||||||
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} />}
|
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} replies={replies || []} />}
|
||||||
<ReplyBacklinks post={reply} />
|
<ReplyBacklinks post={reply} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -252,7 +282,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
const PostMobile = ({ post, roles, showAllReplies, showReplies = true, replies: threadReplies }: PostProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, pinned, postCid, replyCount, state, subplebbitAddress } = post || {};
|
const { author, cid, pinned, postCid, replyCount, state, subplebbitAddress } = post || {};
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||||
@@ -262,14 +292,15 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
|
|||||||
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
||||||
const isInPostView = isPostPageView(location.pathname, params);
|
const isInPostView = isPostPageView(location.pathname, params);
|
||||||
const linksCount = useCountLinksInReplies(post);
|
const linksCount = useCountLinksInReplies(post);
|
||||||
const replies = useReplies(post);
|
const commentReplies = useReplies(post);
|
||||||
|
const replies = threadReplies || commentReplies;
|
||||||
|
|
||||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||||
const { hidden, unhide } = useHide({ cid });
|
const { hidden, unhide } = useHide({ cid });
|
||||||
|
|
||||||
const stateString = useStateString(post) || t('loading_post');
|
const stateString = useStateString(post) || t('loading_post');
|
||||||
|
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
||||||
const showRules = isDescription && subplebbit?.rules && subplebbit?.rules.length > 0;
|
const showRules = isDescription && subplebbit?.rules && subplebbit?.rules.length > 0;
|
||||||
const subplebbitRulesReply = {
|
const subplebbitRulesReply = {
|
||||||
isRules: true,
|
isRules: true,
|
||||||
@@ -307,8 +338,8 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
|
|||||||
data-post-cid={postCid}
|
data-post-cid={postCid}
|
||||||
>
|
>
|
||||||
{shouldShowSnow() && <img src={`${process.env.PUBLIC_URL}/assets/xmashat.gif`} className={styles.xmasHat} alt='' />}
|
{shouldShowSnow() && <img src={`${process.env.PUBLIC_URL}/assets/xmashat.gif`} className={styles.xmasHat} alt='' />}
|
||||||
<PostInfoAndMedia openReplyModal={openReplyModal} post={post} postReplyCount={replyCount} roles={roles} />
|
<PostInfoAndMedia post={post} postReplyCount={replyCount} roles={roles} />
|
||||||
<CommentContent comment={post} />
|
<CommentContent comment={post} replies={replies} />
|
||||||
</div>
|
</div>
|
||||||
{!isInPostView && !isInPendingPostView && showReplies && (
|
{!isInPostView && !isInPendingPostView && showReplies && (
|
||||||
<div className={styles.postLink}>
|
<div className={styles.postLink}>
|
||||||
@@ -331,7 +362,7 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
|
|||||||
showReplies &&
|
showReplies &&
|
||||||
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => (
|
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => (
|
||||||
<div key={index} className={styles.replyContainer}>
|
<div key={index} className={styles.replyContainer}>
|
||||||
<Reply openReplyModal={openReplyModal} postReplyCount={replyCount} reply={reply} roles={roles} />
|
<Reply postReplyCount={replyCount} reply={reply} roles={roles} replies={replies} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{showRules && (
|
{showRules && (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
z-index: 3;
|
z-index: 999;
|
||||||
background-color: var(--challenge-modal-background-color);
|
background-color: var(--challenge-modal-background-color);
|
||||||
border: var(--challenge-modal-border);
|
border: var(--challenge-modal-border);
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
|
|||||||
@@ -147,11 +147,19 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, postCid, scrollY, s
|
|||||||
|
|
||||||
const [{ x, y }, api] = useSpring(() => ({
|
const [{ x, y }, api] = useSpring(() => ({
|
||||||
x: window.innerWidth / 2 - 150,
|
x: window.innerWidth / 2 - 150,
|
||||||
y: window.innerHeight / 2 - 400,
|
y: window.innerHeight / 2 - 200,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const bind = useDrag(
|
const bind = useDrag(
|
||||||
({ offset: [ox, oy] }) => {
|
({ active, event, offset: [ox, oy] }) => {
|
||||||
|
if (active) {
|
||||||
|
event.preventDefault();
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
document.body.style.webkitUserSelect = 'none';
|
||||||
|
} else {
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
document.body.style.webkitUserSelect = '';
|
||||||
|
}
|
||||||
api.start({ x: ox, y: oy, immediate: true });
|
api.start({ x: ox, y: oy, immediate: true });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface ReplyQuotePreviewProps {
|
|||||||
backlinkReply?: Comment;
|
backlinkReply?: Comment;
|
||||||
isQuotelinkReply?: boolean;
|
isQuotelinkReply?: boolean;
|
||||||
quotelinkReply?: Comment;
|
quotelinkReply?: Comment;
|
||||||
|
replies?: Comment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
||||||
@@ -53,7 +54,7 @@ const handleQuoteHover = (cid: string, onElementOutOfView: () => void) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => {
|
const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply, replies }: ReplyQuotePreviewProps) => {
|
||||||
const [hoveredCid, setHoveredCid] = useState<string | null>(null);
|
const [hoveredCid, setHoveredCid] = useState<string | null>(null);
|
||||||
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
|
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
|
||||||
const placementRef = useRef<Placement>('right');
|
const placementRef = useRef<Placement>('right');
|
||||||
@@ -144,7 +145,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
|||||||
outOfViewCid === backlinkReply?.cid &&
|
outOfViewCid === backlinkReply?.cid &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
||||||
<Post post={backlinkReply} showReplies={false} />
|
<Post post={backlinkReply} showReplies={false} replies={replies} />
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
)}
|
)}
|
||||||
@@ -173,7 +174,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
|||||||
outOfViewCid === quotelinkReply?.cid &&
|
outOfViewCid === quotelinkReply?.cid &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
||||||
<Post post={quotelinkReply} showReplies={false} />
|
<Post post={quotelinkReply} showReplies={false} replies={replies} />
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
)}
|
)}
|
||||||
@@ -183,7 +184,7 @@ const DesktopQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, i
|
|||||||
return isBacklinkReply ? replyBacklink : isQuotelinkReply && replyQuotelink;
|
return isBacklinkReply ? replyBacklink : isQuotelinkReply && replyQuotelink;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => {
|
const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply, replies }: ReplyQuotePreviewProps) => {
|
||||||
const [hoveredCid, setHoveredCid] = useState<string | null>(null);
|
const [hoveredCid, setHoveredCid] = useState<string | null>(null);
|
||||||
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
|
const [outOfViewCid, setOutOfViewCid] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -256,7 +257,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
|||||||
outOfViewCid === backlinkReply?.cid &&
|
outOfViewCid === backlinkReply?.cid &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
||||||
<Post post={backlinkReply} showReplies={false} />
|
<Post post={backlinkReply} showReplies={false} replies={replies} />
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
)}
|
)}
|
||||||
@@ -292,7 +293,7 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
|||||||
outOfViewCid === quotelinkReply?.cid &&
|
outOfViewCid === quotelinkReply?.cid &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
<div className={styles.replyQuotePreview} ref={refs.setFloating} style={floatingStyles}>
|
||||||
<Post post={quotelinkReply} showReplies={false} />
|
<Post post={quotelinkReply} showReplies={false} replies={replies} />
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
)}
|
)}
|
||||||
@@ -302,13 +303,25 @@ const MobileQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, is
|
|||||||
return isBacklinkReply ? replyBacklink : isQuotelinkReply && replyQuotelink;
|
return isBacklinkReply ? replyBacklink : isQuotelinkReply && replyQuotelink;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReplyQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply }: ReplyQuotePreviewProps) => {
|
const ReplyQuotePreview = ({ backlinkReply, quotelinkReply, isBacklinkReply, isQuotelinkReply, replies }: ReplyQuotePreviewProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
return isMobile ? (
|
return isMobile ? (
|
||||||
<MobileQuotePreview backlinkReply={backlinkReply} quotelinkReply={quotelinkReply} isBacklinkReply={isBacklinkReply} isQuotelinkReply={isQuotelinkReply} />
|
<MobileQuotePreview
|
||||||
|
backlinkReply={backlinkReply}
|
||||||
|
quotelinkReply={quotelinkReply}
|
||||||
|
isBacklinkReply={isBacklinkReply}
|
||||||
|
isQuotelinkReply={isQuotelinkReply}
|
||||||
|
replies={replies}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DesktopQuotePreview backlinkReply={backlinkReply} quotelinkReply={quotelinkReply} isBacklinkReply={isBacklinkReply} isQuotelinkReply={isQuotelinkReply} />
|
<DesktopQuotePreview
|
||||||
|
backlinkReply={backlinkReply}
|
||||||
|
quotelinkReply={quotelinkReply}
|
||||||
|
isBacklinkReply={isBacklinkReply}
|
||||||
|
isQuotelinkReply={isQuotelinkReply}
|
||||||
|
replies={replies}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ const AvatarSettings = () => {
|
|||||||
components={{
|
components={{
|
||||||
1: (
|
1: (
|
||||||
<Link
|
<Link
|
||||||
|
key='token-address-link'
|
||||||
to='https://github.com/plebbit/plebbit-react-hooks/blob/557cc3f40b5933a00553ed9c0bc310d2cd7a3b52/src/hooks/authors/author-avatars.ts#L133'
|
to='https://github.com/plebbit/plebbit-react-hooks/blob/557cc3f40b5933a00553ed9c0bc310d2cd7a3b52/src/hooks/authors/author-avatars.ts#L133'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
@@ -177,9 +178,9 @@ const AvatarSettings = () => {
|
|||||||
i18nKey='copy_message_etherscan'
|
i18nKey='copy_message_etherscan'
|
||||||
values={{ copy: hasCopied ? t('copied') : t('copy') }}
|
values={{ copy: hasCopied ? t('copied') : t('copy') }}
|
||||||
components={{
|
components={{
|
||||||
1: <button onClick={copyMessageToSign} />,
|
1: <button key='copy-message-button' onClick={copyMessageToSign} />,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
2: <a href='https://etherscan.io/verifiedSignatures' target='_blank' rel='noopener noreferrer' />,
|
2: <a key='etherscan-link' href='https://etherscan.io/verifiedSignatures' target='_blank' rel='noopener noreferrer' />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+5
-1
@@ -24,4 +24,8 @@
|
|||||||
.button:hover {
|
.button:hover {
|
||||||
color: var(--button-desktop-text-color-hover);
|
color: var(--button-desktop-text-color-hover);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notBlocked {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ const BlockedAddressesSetting = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
t('you_have_not_blocked')
|
<div className={styles.notBlocked}>{t('you_have_not_blocked')}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import useSpecialThemeStore from '../../../stores/use-special-theme-store';
|
|||||||
import { isChristmas } from '../../../lib/utils/time-utils';
|
import { isChristmas } from '../../../lib/utils/time-utils';
|
||||||
import Version from '../../version';
|
import Version from '../../version';
|
||||||
|
|
||||||
const commitRef = process.env.REACT_APP_COMMIT_REF;
|
const commitRef = process.env.VITE_COMMIT_REF;
|
||||||
const isElectron = window.isElectron === true;
|
const isElectron = window.isElectron === true;
|
||||||
|
|
||||||
const CheckForUpdates = () => {
|
const CheckForUpdates = () => {
|
||||||
|
|||||||
+5
-1
@@ -24,4 +24,8 @@
|
|||||||
.button:hover {
|
.button:hover {
|
||||||
color: var(--button-desktop-text-color-hover);
|
color: var(--button-desktop-text-color-hover);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notSubscribed {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const SubscriptionsSetting = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
t('not_subscribed_to_any_board')
|
<div className={styles.notSubscribed}>{t('not_subscribed_to_any_board')}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ const SubplebbitStats = () => {
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey='board_stats_hour'
|
i18nKey='board_stats_hour'
|
||||||
values={{ userCount: stats.hourActiveUserCount ?? '?', postCount: stats.hourPostCount ?? '?' }}
|
values={{ userCount: stats.hourActiveUserCount ?? '?', postCount: stats.hourPostCount ?? '?' }}
|
||||||
components={{ 1: <span className={styles.statValue} /> }}
|
components={{ 1: <span key='hour-stat-value' className={styles.statValue} /> }}
|
||||||
/>
|
/>
|
||||||
{' / '}
|
{' / '}
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey='board_stats_day'
|
i18nKey='board_stats_day'
|
||||||
values={{ userCount: stats.dayActiveUserCount ?? '?', postCount: stats.dayPostCount ?? '?' }}
|
values={{ userCount: stats.dayActiveUserCount ?? '?', postCount: stats.dayPostCount ?? '?' }}
|
||||||
components={{ 1: <span className={styles.statValue} /> }}
|
components={{ 1: <span key='day-stat-value' className={styles.statValue} /> }}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -67,13 +67,13 @@ const SubplebbitStats = () => {
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey='board_stats_week'
|
i18nKey='board_stats_week'
|
||||||
values={{ userCount: stats.weekActiveUserCount ?? '?', postCount: stats.weekPostCount ?? '?' }}
|
values={{ userCount: stats.weekActiveUserCount ?? '?', postCount: stats.weekPostCount ?? '?' }}
|
||||||
components={{ 1: <span className={styles.statValue} /> }}
|
components={{ 1: <span key='week-stat-value' className={styles.statValue} /> }}
|
||||||
/>
|
/>
|
||||||
{' / '}
|
{' / '}
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey='board_stats_month'
|
i18nKey='board_stats_month'
|
||||||
values={{ userCount: stats.monthActiveUserCount ?? '?', postCount: stats.monthPostCount ?? '?' }}
|
values={{ userCount: stats.monthActiveUserCount ?? '?', postCount: stats.monthPostCount ?? '?' }}
|
||||||
components={{ 1: <span className={styles.statValue} /> }}
|
components={{ 1: <span key='month-stat-value' className={styles.statValue} /> }}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -84,7 +84,7 @@ const SubplebbitStats = () => {
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey='board_stats_all'
|
i18nKey='board_stats_all'
|
||||||
values={{ userCount: stats.allActiveUserCount ?? '?', postCount: stats.allPostCount ?? '?' }}
|
values={{ userCount: stats.allActiveUserCount ?? '?', postCount: stats.allPostCount ?? '?' }}
|
||||||
components={{ 1: <span className={styles.statValue} /> }}
|
components={{ 1: <span key='all-stat-value' className={styles.statValue} /> }}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|||||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||||
import { useAccount, useAccountComment } from '@plebbit/plebbit-react-hooks';
|
import { useAccount, useAccountComment, useAccountSubplebbits } from '@plebbit/plebbit-react-hooks';
|
||||||
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||||
import { useAutoSubscribe } from '../../hooks/use-auto-subscribe';
|
import { useAutoSubscribe } from '../../hooks/use-auto-subscribe';
|
||||||
@@ -79,10 +79,20 @@ const TopBarDesktop = () => {
|
|||||||
|
|
||||||
const subscriptions = account?.subscriptions;
|
const subscriptions = account?.subscriptions;
|
||||||
|
|
||||||
|
const { accountSubplebbits } = useAccountSubplebbits();
|
||||||
|
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.boardNavDesktop}>
|
<div className={styles.boardNavDesktop}>
|
||||||
<span className={styles.boardList}>
|
<span className={styles.boardList}>
|
||||||
[<Link to='/p/all'>all</Link> / <Link to='/p/subscriptions'>subscriptions</Link>]{' '}
|
[<Link to='/p/all'>all</Link> / <Link to='/p/subscriptions'>subscriptions</Link>
|
||||||
|
{accountSubplebbitAddresses.length > 0 && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
/ <Link to='/p/mod'>mod</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
]{' '}
|
||||||
{subscriptions?.length > 0 && (
|
{subscriptions?.length > 0 && (
|
||||||
<>
|
<>
|
||||||
[
|
[
|
||||||
@@ -137,11 +147,15 @@ const TopBarMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
|
|||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
||||||
const selectValue = isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : subplebbitAddress;
|
const selectValue = isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : subplebbitAddress;
|
||||||
|
|
||||||
|
const { accountSubplebbits } = useAccountSubplebbits();
|
||||||
|
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
||||||
|
|
||||||
const boardSelect = (
|
const boardSelect = (
|
||||||
<select value={selectValue} onChange={(e) => navigate(`/p/${e.target.value}${isInCatalogView ? '/catalog' : ''}`)}>
|
<select value={selectValue} onChange={(e) => navigate(`/p/${e.target.value}${isInCatalogView ? '/catalog' : ''}`)}>
|
||||||
{!currentSubplebbitIsInList && subplebbitAddress && <option value={subplebbitAddress}>{displaySubplebbitAddress}</option>}
|
{!currentSubplebbitIsInList && subplebbitAddress && <option value={subplebbitAddress}>{displaySubplebbitAddress}</option>}
|
||||||
<option value='all'>all</option>
|
<option value='all'>all</option>
|
||||||
<option value='subscriptions'>subscriptions</option>
|
<option value='subscriptions'>subscriptions</option>
|
||||||
|
{accountSubplebbitAddresses.length > 0 && <option value='mod'>mod</option>}
|
||||||
{subplebbitAddresses.map((address: any, index: number) => {
|
{subplebbitAddresses.map((address: any, index: number) => {
|
||||||
const subplebbitAddress = address?.includes('.') ? address : Plebbit.getShortAddress(address);
|
const subplebbitAddress = address?.includes('.') ? address : Plebbit.getShortAddress(address);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import packageJson from '../../../package.json';
|
import packageJson from '../../../package.json';
|
||||||
|
|
||||||
const { version } = packageJson;
|
const { version } = packageJson;
|
||||||
const commitRef = process.env.REACT_APP_COMMIT_REF;
|
const commitRef = process.env.VITE_COMMIT_REF;
|
||||||
const isElectron = window.isElectron === true;
|
const isElectron = window.isElectron === true;
|
||||||
|
|
||||||
const Version = () => {
|
const Version = () => {
|
||||||
@@ -11,11 +11,11 @@ const Version = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
href={commitRef ? `https://github.com/plebbit/seedit/commit/${commitRef}` : `https://github.com/plebbit/seedit/releases/tag/v${version}`}
|
href={commitRef ? `https://github.com/plebbit/plebchan/commit/${commitRef}` : `https://github.com/plebbit/plebchan/releases/tag/v${version}`}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
>
|
>
|
||||||
v{commitRef ? '-dev' : version}
|
plebchan v{commitRef ? `${version}-dev (#${commitRef.slice(0, 7)})` : version}
|
||||||
</a>
|
</a>
|
||||||
{isElectron && (
|
{isElectron && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// plebchan shouldn't consider 'vulgar' or 'anti' as nsfw tags, unlike more sfw-oriented clients
|
||||||
|
export const nsfwTags = ['adult', 'gore'];
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useAccount, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { useAccount } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import useAnonModeStore from '../stores/use-anon-mode-store';
|
import useAnonModeStore from '../stores/use-anon-mode-store';
|
||||||
import useAnonMode from './use-anon-mode';
|
import useAnonMode from './use-anon-mode';
|
||||||
|
|
||||||
@@ -12,7 +13,8 @@ interface AuthorPrivilegesProps {
|
|||||||
const useAuthorPrivileges = ({ commentAuthorAddress, subplebbitAddress, postCid }: AuthorPrivilegesProps) => {
|
const useAuthorPrivileges = ({ commentAuthorAddress, subplebbitAddress, postCid }: AuthorPrivilegesProps) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const accountAuthorAddress = account?.author?.address;
|
const accountAuthorAddress = account?.author?.address;
|
||||||
const { roles } = useSubplebbit({ subplebbitAddress }) || {};
|
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
||||||
|
const { roles } = subplebbit || {};
|
||||||
const { getAddressSigner, getThreadSigner } = useAnonModeStore();
|
const { getAddressSigner, getThreadSigner } = useAnonModeStore();
|
||||||
const { anonMode } = useAnonMode(postCid);
|
const { anonMode } = useAnonMode(postCid);
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea
|
|||||||
|
|
||||||
// show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update
|
// show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update
|
||||||
const filteredComments = accountComments.filter((comment) => {
|
const filteredComments = accountComments.filter((comment) => {
|
||||||
const { cid, deleted, link, postCid, removed, state, subplebbitAddress, timestamp } = comment || {};
|
const { cid, deleted, link, postCid, removed, state, subplebbitAddress, timestamp, thumbnailUrl, linkWidth, linkHeight } = comment || {};
|
||||||
const commentMediaInfo = getCommentMediaInfo(comment);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const isMediaShowed = getHasThumbnail(commentMediaInfo, link);
|
const isMediaShowed = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
|
||||||
import { getCommentMediaInfo, fetchWebpageThumbnailIfNeeded } from '../lib/utils/media-utils';
|
import { getCommentMediaInfo, fetchWebpageThumbnailIfNeeded } from '../lib/utils/media-utils';
|
||||||
import { isPendingPostView, isPostPageView } from '../lib/utils/view-utils';
|
import { isPendingPostView, isPostPageView } from '../lib/utils/view-utils';
|
||||||
|
|
||||||
export const useCommentMediaInfo = (comment: Comment) => {
|
export const useCommentMediaInfo = (link: string, thumbnailUrl: string, linkWidth: number, linkHeight: number) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||||
@@ -12,7 +11,7 @@ export const useCommentMediaInfo = (comment: Comment) => {
|
|||||||
|
|
||||||
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
||||||
const fetchThumbnail = useCallback(async () => {
|
const fetchThumbnail = useCallback(async () => {
|
||||||
let commentMediaInfo = getCommentMediaInfo(comment);
|
let commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
if (commentMediaInfo?.type === 'webpage' && !commentMediaInfo.thumbnail) {
|
if (commentMediaInfo?.type === 'webpage' && !commentMediaInfo.thumbnail) {
|
||||||
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(commentMediaInfo);
|
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(commentMediaInfo);
|
||||||
// Fetch the dimensions of the thumbnail
|
// Fetch the dimensions of the thumbnail
|
||||||
@@ -30,7 +29,7 @@ export const useCommentMediaInfo = (comment: Comment) => {
|
|||||||
commentMediaInfo = newMediaInfo;
|
commentMediaInfo = newMediaInfo;
|
||||||
}
|
}
|
||||||
return commentMediaInfo;
|
return commentMediaInfo;
|
||||||
}, [comment]);
|
}, [link, thumbnailUrl, linkWidth, linkHeight]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// don't fetch in feed view, it displaces the posts
|
// don't fetch in feed view, it displaces the posts
|
||||||
@@ -39,5 +38,5 @@ export const useCommentMediaInfo = (comment: Comment) => {
|
|||||||
}
|
}
|
||||||
}, [fetchThumbnail, isInPostPageView, isInPendingPostView]);
|
}, [fetchThumbnail, isInPostPageView, isInPendingPostView]);
|
||||||
|
|
||||||
return getCommentMediaInfo(comment);
|
return getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useMemo } from 'react';
|
|||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import useThemeStore from '../stores/use-theme-store';
|
import useThemeStore from '../stores/use-theme-store';
|
||||||
import { useDefaultSubplebbits } from './use-default-subplebbits';
|
import { useDefaultSubplebbits } from './use-default-subplebbits';
|
||||||
import { isAllView, isHomeView, isNotFoundView, isPendingPostView, isSubscriptionsView } from '../lib/utils/view-utils';
|
import { isAllView, isHomeView, isNotFoundView, isPendingPostView, isSubscriptionsView, isModView } from '../lib/utils/view-utils';
|
||||||
import { nsfwTags } from '../views/home/home';
|
import { nsfwTags } from '../constants/nsfwTags';
|
||||||
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
|
||||||
const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
|
const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
|
||||||
@@ -19,6 +19,7 @@ const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
|
|||||||
const isInNotFoundView = isNotFoundView(location.pathname, params);
|
const isInNotFoundView = isNotFoundView(location.pathname, params);
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||||
|
const isInModView = isModView(location.pathname);
|
||||||
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
||||||
|
|
||||||
const initialTheme = useMemo(() => {
|
const initialTheme = useMemo(() => {
|
||||||
@@ -36,7 +37,7 @@ const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
|
|||||||
} else {
|
} else {
|
||||||
theme = currentTheme || 'yotsuba';
|
theme = currentTheme || 'yotsuba';
|
||||||
}
|
}
|
||||||
} else if (isInAllView || isInSubscriptionsView) {
|
} else if (isInAllView || isInSubscriptionsView || isInModView) {
|
||||||
theme = getTheme('sfw', false) || 'yotsuba-b'; // Add 'false' parameter
|
theme = getTheme('sfw', false) || 'yotsuba-b'; // Add 'false' parameter
|
||||||
} else if (isInHomeView || isInNotFoundView) {
|
} else if (isInHomeView || isInNotFoundView) {
|
||||||
theme = 'yotsuba';
|
theme = 'yotsuba';
|
||||||
@@ -54,6 +55,7 @@ const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
|
|||||||
isInPendingPostView,
|
isInPendingPostView,
|
||||||
isInAllView,
|
isInAllView,
|
||||||
isInSubscriptionsView,
|
isInSubscriptionsView,
|
||||||
|
isInModView,
|
||||||
isInHomeView,
|
isInHomeView,
|
||||||
isInNotFoundView,
|
isInNotFoundView,
|
||||||
paramsSubplebbitAddress,
|
paramsSubplebbitAddress,
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
|||||||
|
|
||||||
if (subplebbit?.posts?.pages?.hot?.comments) {
|
if (subplebbit?.posts?.pages?.hot?.comments) {
|
||||||
for (const post of Object.values(subplebbit.posts.pages.hot.comments as Comment)) {
|
for (const post of Object.values(subplebbit.posts.pages.hot.comments as Comment)) {
|
||||||
const { deleted, link, locked, pinned, removed, replyCount, timestamp } = post;
|
const { deleted, link, linkHeight, linkWidth, locked, pinned, removed, replyCount, thumbnailUrl, timestamp } = post;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const commentMediaInfo = getCommentMediaInfo(post);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
+10
-14
@@ -1,10 +1,10 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { isAllView, isSubscriptionsView } from '../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView, isModView } from '../lib/utils/view-utils';
|
||||||
import useThemeStore from '../stores/use-theme-store';
|
import useThemeStore from '../stores/use-theme-store';
|
||||||
import { useDefaultSubplebbits } from './use-default-subplebbits';
|
import { useDefaultSubplebbits } from './use-default-subplebbits';
|
||||||
import useInitialTheme from './use-initial-theme';
|
import useInitialTheme from './use-initial-theme';
|
||||||
import { nsfwTags } from '../views/home/home';
|
import { nsfwTags } from '../constants/nsfwTags';
|
||||||
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSpecialThemeStore from '../stores/use-special-theme-store';
|
import useSpecialThemeStore from '../stores/use-special-theme-store';
|
||||||
import { isChristmas } from '../lib/utils/time-utils';
|
import { isChristmas } from '../lib/utils/time-utils';
|
||||||
@@ -37,26 +37,24 @@ const useTheme = (): [string, (theme: string) => void] => {
|
|||||||
|
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||||
|
const isInModView = isModView(location.pathname);
|
||||||
|
const subplebbitAddress = params?.subplebbitAddress || pendingPostSubplebbitAddress;
|
||||||
|
|
||||||
// Check for Christmas and initialize special theme if needed
|
// Check for Christmas and initialize special theme if needed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isChristmasTime = isChristmas();
|
const isChristmasTime = isChristmas();
|
||||||
const subplebbitAddress = params?.subplebbitAddress || pendingPostSubplebbitAddress;
|
|
||||||
|
|
||||||
if (isChristmasTime && isEnabled === null && subplebbitAddress && !isInAllView && !isInSubscriptionsView) {
|
if (isChristmasTime && isEnabled === null && subplebbitAddress && !isInAllView && !isInSubscriptionsView && !isInModView) {
|
||||||
setIsEnabled(true);
|
setIsEnabled(true);
|
||||||
setCurrentTheme('tomorrow');
|
setCurrentTheme('tomorrow');
|
||||||
updateThemeClass('tomorrow');
|
updateThemeClass('tomorrow');
|
||||||
} else if (!isChristmasTime && isEnabled) {
|
} else if (!isChristmasTime && isEnabled) {
|
||||||
setIsEnabled(false);
|
setIsEnabled(false);
|
||||||
}
|
}
|
||||||
}, [isEnabled, setIsEnabled, params, pendingPostSubplebbitAddress, location.pathname, isInAllView, isInSubscriptionsView]);
|
}, [isEnabled, setIsEnabled, params, pendingPostSubplebbitAddress, location.pathname, isInAllView, isInSubscriptionsView, isInModView, subplebbitAddress]);
|
||||||
|
|
||||||
const getCurrentTheme = useCallback(() => {
|
const getCurrentTheme = useCallback(() => {
|
||||||
const { isEnabled } = useSpecialThemeStore.getState();
|
const { isEnabled } = useSpecialThemeStore.getState();
|
||||||
const subplebbitAddress = params?.subplebbitAddress || pendingPostSubplebbitAddress;
|
|
||||||
const isInAllView = isAllView(location.pathname);
|
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
|
||||||
|
|
||||||
// Always use yotsuba for home page
|
// Always use yotsuba for home page
|
||||||
if (location.pathname === '/') {
|
if (location.pathname === '/') {
|
||||||
@@ -69,7 +67,7 @@ const useTheme = (): [string, (theme: string) => void] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let storedTheme = null;
|
let storedTheme = null;
|
||||||
if (isInAllView || isInSubscriptionsView) {
|
if (isInAllView || isInSubscriptionsView || isInModView) {
|
||||||
storedTheme = getTheme('sfw', false);
|
storedTheme = getTheme('sfw', false);
|
||||||
} else if (subplebbitAddress) {
|
} else if (subplebbitAddress) {
|
||||||
const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress);
|
const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress);
|
||||||
@@ -81,6 +79,7 @@ const useTheme = (): [string, (theme: string) => void] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return storedTheme || initialTheme;
|
return storedTheme || initialTheme;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [location.pathname, params, getTheme, subplebbits, initialTheme, pendingPostSubplebbitAddress]);
|
}, [location.pathname, params, getTheme, subplebbits, initialTheme, pendingPostSubplebbitAddress]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -97,11 +96,7 @@ const useTheme = (): [string, (theme: string) => void] => {
|
|||||||
|
|
||||||
const setSubplebbitTheme = useCallback(
|
const setSubplebbitTheme = useCallback(
|
||||||
async (newTheme: string) => {
|
async (newTheme: string) => {
|
||||||
const subplebbitAddress = params?.subplebbitAddress || pendingPostSubplebbitAddress;
|
if (isInAllView || isInSubscriptionsView || isInModView) {
|
||||||
const isInAllView = isAllView(location.pathname);
|
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
|
||||||
|
|
||||||
if (isInAllView || isInSubscriptionsView) {
|
|
||||||
await setThemeStore('sfw', newTheme);
|
await setThemeStore('sfw', newTheme);
|
||||||
} else if (subplebbitAddress) {
|
} else if (subplebbitAddress) {
|
||||||
const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress);
|
const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress);
|
||||||
@@ -115,6 +110,7 @@ const useTheme = (): [string, (theme: string) => void] => {
|
|||||||
setCurrentTheme(newTheme);
|
setCurrentTheme(newTheme);
|
||||||
updateThemeClass(newTheme);
|
updateThemeClass(newTheme);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[location.pathname, params, setThemeStore, subplebbits, pendingPostSubplebbitAddress],
|
[location.pathname, params, setThemeStore, subplebbits, pendingPostSubplebbitAddress],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -53,11 +53,11 @@ hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.yellowOfflineIcon {
|
.yellowOfflineIcon {
|
||||||
background-image: url('/public/assets/icons/offline-blue.png');
|
background-image: url('/assets/icons/offline-blue.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.redOfflineIcon {
|
.redOfflineIcon {
|
||||||
background-image: url('/public/assets/icons/offline-red.png');
|
background-image: url('/assets/icons/offline-red.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import './polyfills.js';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './app';
|
import App from './app';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import localForageLru from '@plebbit/plebbit-react-hooks/dist/lib/localforage-lru/index.js';
|
import localForageLru from '@plebbit/plebbit-react-hooks/dist/lib/localforage-lru/index.js';
|
||||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
|
||||||
import extName from 'ext-name';
|
import extName from 'ext-name';
|
||||||
import { canEmbed } from '../../components/embed';
|
import { canEmbed } from '../../components/embed';
|
||||||
import memoize from 'memoizee';
|
import memoize from 'memoizee';
|
||||||
@@ -13,7 +12,8 @@ export interface CommentMediaInfo {
|
|||||||
thumbnailWidth?: number;
|
thumbnailWidth?: number;
|
||||||
thumbnailHeight?: number;
|
thumbnailHeight?: number;
|
||||||
patternThumbnailUrl?: string;
|
patternThumbnailUrl?: string;
|
||||||
post?: Comment;
|
linkWidth?: number;
|
||||||
|
linkHeight?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDisplayMediaInfoType = (type: string, t: any) => {
|
export const getDisplayMediaInfoType = (type: string, t: any) => {
|
||||||
@@ -35,19 +35,20 @@ export const getDisplayMediaInfoType = (type: string, t: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHasThumbnail = (commentMediaInfo: CommentMediaInfo | undefined, link: string | undefined): boolean => {
|
export const getHasThumbnail = memoize(
|
||||||
const iframeThumbnail = commentMediaInfo?.patternThumbnailUrl || commentMediaInfo?.thumbnail;
|
(commentMediaInfo: CommentMediaInfo | undefined, link: string | undefined): boolean => {
|
||||||
return link &&
|
if (!link || !commentMediaInfo) return false;
|
||||||
commentMediaInfo &&
|
|
||||||
(commentMediaInfo.type === 'image' ||
|
const { type, thumbnail, patternThumbnailUrl } = commentMediaInfo;
|
||||||
commentMediaInfo.type === 'video' ||
|
|
||||||
commentMediaInfo.type === 'audio' ||
|
if (type === 'image' || type === 'video' || type === 'audio' || type === 'gif') return true;
|
||||||
commentMediaInfo.type === 'gif' ||
|
if (type === 'webpage' && thumbnail) return true;
|
||||||
(commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail) ||
|
if (type === 'iframe' && (patternThumbnailUrl || thumbnail)) return true;
|
||||||
(commentMediaInfo.type === 'iframe' && iframeThumbnail))
|
|
||||||
? true
|
return false;
|
||||||
: false;
|
},
|
||||||
};
|
{ max: 1000 },
|
||||||
|
);
|
||||||
|
|
||||||
const getYouTubeVideoId = (url: URL): string | null => {
|
const getYouTubeVideoId = (url: URL): string | null => {
|
||||||
if (url.host.includes('youtu.be')) {
|
if (url.host.includes('youtu.be')) {
|
||||||
@@ -182,55 +183,61 @@ const fetchWebpageThumbnail = async (url: string): Promise<string | undefined> =
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCommentMediaInfo = (comment: Comment): CommentMediaInfo | undefined => {
|
export const getCommentMediaInfo = (link: string, thumbnailUrl: string, linkWidth: number, linkHeight: number): CommentMediaInfo | undefined => {
|
||||||
if (!comment?.thumbnailUrl && !comment?.link) {
|
if (!thumbnailUrl && !link) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const linkInfo = comment.link ? getLinkMediaInfo(comment.link) : undefined;
|
const linkInfo = link ? getLinkMediaInfo(link) : undefined;
|
||||||
if (linkInfo) {
|
if (linkInfo) {
|
||||||
return {
|
return {
|
||||||
...linkInfo,
|
...linkInfo,
|
||||||
thumbnail: comment.thumbnailUrl || linkInfo.thumbnail,
|
thumbnail: thumbnailUrl || linkInfo.thumbnail,
|
||||||
post: comment,
|
linkWidth,
|
||||||
|
linkHeight,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMediaDimensions = (commentMediaInfo: CommentMediaInfo | undefined): string => {
|
const EMBED_DIMENSIONS = {
|
||||||
if (!commentMediaInfo) return '';
|
'youtube.com': '800x450',
|
||||||
|
'youtu.be': '800x450',
|
||||||
|
'instagram.com': '360x420',
|
||||||
|
'reddit.com': '500x520',
|
||||||
|
'tiktok.com': '400x780',
|
||||||
|
'x.com': '550x580',
|
||||||
|
'twitter.com': '550x580',
|
||||||
|
'soundcloud.com': '700x166',
|
||||||
|
} as const;
|
||||||
|
|
||||||
const { type, url, post } = commentMediaInfo;
|
export const getMediaDimensions = memoize(
|
||||||
|
(commentMediaInfo: CommentMediaInfo | undefined): string => {
|
||||||
|
if (!commentMediaInfo) return '';
|
||||||
|
|
||||||
if (type === 'iframe' && url) {
|
const { type, url, linkWidth, linkHeight } = commentMediaInfo;
|
||||||
const embedUrl = new URL(url);
|
|
||||||
if (canEmbed(embedUrl)) {
|
if (type === 'iframe' && url) {
|
||||||
// hardcoded dimensions from embed.module.css
|
const embedUrl = new URL(url);
|
||||||
if (embedUrl.hostname.includes('youtube.com') || embedUrl.hostname.includes('youtu.be')) {
|
if (canEmbed(embedUrl)) {
|
||||||
return '800x450';
|
const hostname = embedUrl.hostname;
|
||||||
} else if (embedUrl.hostname.includes('instagram.com')) {
|
for (const [site, dimensions] of Object.entries(EMBED_DIMENSIONS)) {
|
||||||
return '360x420';
|
if (hostname.includes(site)) {
|
||||||
} else if (embedUrl.hostname.includes('reddit.com')) {
|
return dimensions;
|
||||||
return '500x520';
|
}
|
||||||
} else if (embedUrl.hostname.includes('tiktok.com')) {
|
}
|
||||||
return '400x780';
|
}
|
||||||
} else if (embedUrl.hostname.includes('x.com') || embedUrl.hostname.includes('twitter.com')) {
|
} else if (type === 'audio') {
|
||||||
return '550x580';
|
return '700x240';
|
||||||
} else if (embedUrl.hostname.includes('soundcloud.com')) {
|
} else if (type === 'image' || type === 'video' || type === 'gif') {
|
||||||
return '700x166';
|
if (linkWidth && linkHeight) {
|
||||||
|
return `${linkWidth}x${linkHeight}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type === 'audio') {
|
|
||||||
return '700x240'; // hardcoded dimensions from embed.module.css
|
|
||||||
} else if (type === 'image' || type === 'video' || type === 'gif') {
|
|
||||||
// media dimensions calculated by API
|
|
||||||
if (post?.linkWidth && post?.linkHeight) {
|
|
||||||
return `${post.linkWidth}x${post.linkHeight}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
};
|
},
|
||||||
|
{ max: 1000 },
|
||||||
|
);
|
||||||
|
|
||||||
const thumbnailUrlsDb = localForageLru.createInstance({ name: 'plebchanThumbnailUrls', size: 500 });
|
const thumbnailUrlsDb = localForageLru.createInstance({ name: 'plebchanThumbnailUrls', size: 500 });
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ export const isHomeView = (pathname: string): boolean => {
|
|||||||
return pathname === '/';
|
return pathname === '/';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isModView = (pathname: string): boolean => {
|
||||||
|
return pathname === `/p/mod` || pathname.startsWith(`/p/mod/`);
|
||||||
|
};
|
||||||
|
|
||||||
export const isPendingPostView = (pathname: string, params: ParamsType): boolean => {
|
export const isPendingPostView = (pathname: string, params: ParamsType): boolean => {
|
||||||
return pathname === `/profile/${params.accountCommentIndex}` || pathname === `/profile/${params.accountCommentIndex}/settings`;
|
return pathname === `/profile/${params.accountCommentIndex}` || pathname === `/profile/${params.accountCommentIndex}/settings`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Polyfills for Node.js built-ins
|
||||||
|
import { Buffer } from 'buffer';
|
||||||
|
import process from 'process';
|
||||||
|
import 'isomorphic-fetch';
|
||||||
|
|
||||||
|
window.Buffer = Buffer;
|
||||||
|
window.process = process;
|
||||||
|
window.global = window;
|
||||||
|
|
||||||
|
// For ethers.js
|
||||||
|
window.process.version = ''; // Fake Node.js version
|
||||||
|
window.process.env = window.process.env || {};
|
||||||
|
|
||||||
|
// Add any missing fetch polyfill
|
||||||
|
if (!window.fetch) {
|
||||||
|
console.warn('Fetch API is not available, using polyfill');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For crypto support
|
||||||
|
if (typeof window.crypto === 'undefined') {
|
||||||
|
window.crypto = {};
|
||||||
|
}
|
||||||
|
if (typeof window.crypto.getRandomValues === 'undefined') {
|
||||||
|
window.crypto.getRandomValues = function (array) {
|
||||||
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
array[i] = Math.floor(Math.random() * 256);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -53,7 +53,9 @@ const useCatalogFiltersStore = create(
|
|||||||
// filterItems
|
// filterItems
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
const hasThumbnail = getHasThumbnail(getCommentMediaInfo(comment), comment?.link);
|
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {};
|
||||||
|
|
||||||
|
const hasThumbnail = getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link);
|
||||||
// const title = comment?.title?.toLowerCase() || '';
|
// const title = comment?.title?.toLowerCase() || '';
|
||||||
// const content = comment?.content?.toLowerCase() || '';
|
// const content = comment?.content?.toLowerCase() || '';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import useSelectedTextStore from './use-selected-text-store';
|
||||||
|
|
||||||
|
interface ReplyModalState {
|
||||||
|
showReplyModal: boolean;
|
||||||
|
activeCid: string | null;
|
||||||
|
threadCid: string | null;
|
||||||
|
subplebbitAddress: string | null;
|
||||||
|
scrollY: number;
|
||||||
|
closeModal: () => void;
|
||||||
|
openReplyModal: (parentCid: string, postCid: string, subplebbitAddress: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useReplyModalStore = create<ReplyModalState>((set, get) => ({
|
||||||
|
showReplyModal: false,
|
||||||
|
activeCid: null,
|
||||||
|
threadCid: null,
|
||||||
|
subplebbitAddress: null,
|
||||||
|
scrollY: 0,
|
||||||
|
|
||||||
|
closeModal: () => {
|
||||||
|
// Reset selected text if you're using that store
|
||||||
|
useSelectedTextStore.getState().resetSelectedText();
|
||||||
|
set({
|
||||||
|
showReplyModal: false,
|
||||||
|
activeCid: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
openReplyModal: (parentCid, postCid, subplebbitAddress) => {
|
||||||
|
// Get selected text
|
||||||
|
const text = document.getSelection()?.toString();
|
||||||
|
if (text) {
|
||||||
|
useSelectedTextStore.getState().setSelectedText(`>${text}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle mobile scrollY
|
||||||
|
const isMobile = window.innerWidth <= 768; // Simple check, adjust as needed
|
||||||
|
const scrollY = isMobile ? window.scrollY : 0;
|
||||||
|
|
||||||
|
// Don't update if already open with different parent
|
||||||
|
if (get().activeCid && get().activeCid !== parentCid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
activeCid: parentCid,
|
||||||
|
threadCid: postCid,
|
||||||
|
showReplyModal: true,
|
||||||
|
subplebbitAddress,
|
||||||
|
scrollY,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useReplyModalStore;
|
||||||
+43
-43
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
/* body */
|
/* body */
|
||||||
--body-background-base-color: #ffe;
|
--body-background-base-color: #ffe;
|
||||||
--body-background-pattern-image: url("/public/assets/background-fade.png");
|
--body-background-pattern-image: url("/assets/background-fade.png");
|
||||||
--body-font-color: maroon;
|
--body-font-color: maroon;
|
||||||
--body-font-family: arial, helvetica, sans-serif;
|
--body-font-family: arial, helvetica, sans-serif;
|
||||||
--body-font-size: 10pt;
|
--body-font-size: 10pt;
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
/* desktop buttons */
|
/* desktop buttons */
|
||||||
--button-desktop-text-color: #00e;
|
--button-desktop-text-color: #00e;
|
||||||
--button-desktop-text-color-hover: red;
|
--button-desktop-text-color-hover: red;
|
||||||
--close-button-background-image: url("/public/assets/buttons/cross-red.png");
|
--close-button-background-image: url("/assets/buttons/cross-red.png");
|
||||||
|
|
||||||
/* horizontal rule */
|
/* horizontal rule */
|
||||||
--hr-border: none;
|
--hr-border: none;
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
--button-font-size-mobile: 10pt;
|
--button-font-size-mobile: 10pt;
|
||||||
--button-font-weight-mobile: 700;
|
--button-font-weight-mobile: 700;
|
||||||
--button-text-color-mobile: #800;
|
--button-text-color-mobile: #800;
|
||||||
--button-background-image-mobile: url("/public/assets/buttons/button-fade.png");
|
--button-background-image-mobile: url("/assets/buttons/button-fade.png");
|
||||||
--button-background-color-mobile: #f0e0d6;
|
--button-background-color-mobile: #f0e0d6;
|
||||||
--button-border-mobile: 1px solid #c0a69d;
|
--button-border-mobile: 1px solid #c0a69d;
|
||||||
|
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
--post-capcode-mod-text-color: purple;
|
--post-capcode-mod-text-color: purple;
|
||||||
|
|
||||||
/* post desktop */
|
/* post desktop */
|
||||||
--post-hide-button-background-image: url("/public/assets/buttons/minus-red.png");
|
--post-hide-button-background-image: url("/assets/buttons/minus-red.png");
|
||||||
--post-unhide-button-background-image: url("/public/assets/buttons/plus-red.png");
|
--post-unhide-button-background-image: url("/assets/buttons/plus-red.png");
|
||||||
--post-subject-text-color: #cc1105;
|
--post-subject-text-color: #cc1105;
|
||||||
--post-subject-font-weight: 700;
|
--post-subject-font-weight: 700;
|
||||||
--post-name-text-color: #117743;
|
--post-name-text-color: #117743;
|
||||||
@@ -190,9 +190,9 @@
|
|||||||
--settings-modal-border-right: 1px solid #d9bfb7;
|
--settings-modal-border-right: 1px solid #d9bfb7;
|
||||||
--settings-modal-border-bottom: 1px solid #d9bfb7;
|
--settings-modal-border-bottom: 1px solid #d9bfb7;
|
||||||
--settings-modal-border-left: none;
|
--settings-modal-border-left: none;
|
||||||
--settings-modal-close-button-background-image: url("/public/assets/buttons/cross-red.png");
|
--settings-modal-close-button-background-image: url("/assets/buttons/cross-red.png");
|
||||||
--settings-modal-show-button-background-image: url("/public/assets/buttons/plus-red.png");
|
--settings-modal-show-button-background-image: url("/assets/buttons/plus-red.png");
|
||||||
--settings-modal-hide-button-background-image: url("/public/assets/buttons/minus-red.png");
|
--settings-modal-hide-button-background-image: url("/assets/buttons/minus-red.png");
|
||||||
|
|
||||||
/* stats */
|
/* stats */
|
||||||
--stats-font-size: 11px;
|
--stats-font-size: 11px;
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
|
|
||||||
/* body */
|
/* body */
|
||||||
--body-background-base-color: #eef2ff;
|
--body-background-base-color: #eef2ff;
|
||||||
--body-background-pattern-image: url("/public/assets/background-fade-blue.png");
|
--body-background-pattern-image: url("/assets/background-fade-blue.png");
|
||||||
--body-font-color: #000;
|
--body-font-color: #000;
|
||||||
--body-font-family: arial, helvetica, sans-serif;
|
--body-font-family: arial, helvetica, sans-serif;
|
||||||
--body-font-size: 10pt;
|
--body-font-size: 10pt;
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
/* desktop buttons */
|
/* desktop buttons */
|
||||||
--button-desktop-text-color: #34345c;
|
--button-desktop-text-color: #34345c;
|
||||||
--button-desktop-text-color-hover: #d00;
|
--button-desktop-text-color-hover: #d00;
|
||||||
--close-button-background-image: url("/public/assets/buttons/cross-blue.png");
|
--close-button-background-image: url("/assets/buttons/cross-blue.png");
|
||||||
|
|
||||||
/* footer */
|
/* footer */
|
||||||
--footer-item-background-color-desktop: #eef2ff;
|
--footer-item-background-color-desktop: #eef2ff;
|
||||||
@@ -302,7 +302,7 @@
|
|||||||
--button-font-size-mobile: 10pt;
|
--button-font-size-mobile: 10pt;
|
||||||
--button-font-weight-mobile: 700;
|
--button-font-weight-mobile: 700;
|
||||||
--button-text-color-mobile: #34345c;
|
--button-text-color-mobile: #34345c;
|
||||||
--button-background-image-mobile: url("/public/assets/buttons/button-fade-blue.png");
|
--button-background-image-mobile: url("/assets/buttons/button-fade-blue.png");
|
||||||
--button-background-color-mobile: #d6daf0;
|
--button-background-color-mobile: #d6daf0;
|
||||||
--button-border-mobile: 1px solid #b7c5d9;
|
--button-border-mobile: 1px solid #b7c5d9;
|
||||||
|
|
||||||
@@ -310,8 +310,8 @@
|
|||||||
--post-capcode-mod-text-color: purple;
|
--post-capcode-mod-text-color: purple;
|
||||||
|
|
||||||
/* post desktop */
|
/* post desktop */
|
||||||
--post-hide-button-background-image: url("/public/assets/buttons/minus-blue.png");
|
--post-hide-button-background-image: url("/assets/buttons/minus-blue.png");
|
||||||
--post-unhide-button-background-image: url("/public/assets/buttons/plus-blue.png");
|
--post-unhide-button-background-image: url("/assets/buttons/plus-blue.png");
|
||||||
--post-subject-text-color: #0f0c5d;
|
--post-subject-text-color: #0f0c5d;
|
||||||
--post-subject-font-weight: 700;
|
--post-subject-font-weight: 700;
|
||||||
--post-name-text-color: #117743;
|
--post-name-text-color: #117743;
|
||||||
@@ -402,9 +402,9 @@
|
|||||||
--settings-modal-border-bottom: 1px solid #b7c5d9;
|
--settings-modal-border-bottom: 1px solid #b7c5d9;
|
||||||
--settings-modal-border-left: none;
|
--settings-modal-border-left: none;
|
||||||
--settings-modal-header-border-bottom: 1px solid #b7c5d9;
|
--settings-modal-header-border-bottom: 1px solid #b7c5d9;
|
||||||
--settings-modal-close-button-background-image: url("/public/assets/buttons/cross-blue.png");
|
--settings-modal-close-button-background-image: url("/assets/buttons/cross-blue.png");
|
||||||
--settings-modal-show-button-background-image: url("/public/assets/buttons/plus-blue.png");
|
--settings-modal-show-button-background-image: url("/assets/buttons/plus-blue.png");
|
||||||
--settings-modal-hide-button-background-image: url("/public/assets/buttons/minus-blue.png");
|
--settings-modal-hide-button-background-image: url("/assets/buttons/minus-blue.png");
|
||||||
|
|
||||||
/* stats */
|
/* stats */
|
||||||
--stats-font-size: 11px;
|
--stats-font-size: 11px;
|
||||||
@@ -478,7 +478,7 @@
|
|||||||
--button-desktop-text-color: #00e;
|
--button-desktop-text-color: #00e;
|
||||||
--button-desktop-text-color-hover: red;
|
--button-desktop-text-color-hover: red;
|
||||||
--button-text-decoration: underline;
|
--button-text-decoration: underline;
|
||||||
--close-button-background-image: url("/public/assets/buttons/cross-red.png");
|
--close-button-background-image: url("/assets/buttons/cross-red.png");
|
||||||
|
|
||||||
/* footer */
|
/* footer */
|
||||||
--footer-item-background-color-desktop: #fed;
|
--footer-item-background-color-desktop: #fed;
|
||||||
@@ -498,7 +498,7 @@
|
|||||||
--button-font-size-mobile: 11pt;
|
--button-font-size-mobile: 11pt;
|
||||||
--button-font-weight-mobile: 700;
|
--button-font-weight-mobile: 700;
|
||||||
--button-text-color-mobile: #800;
|
--button-text-color-mobile: #800;
|
||||||
--button-background-image-mobile: url("/public/assets/buttons/button-fade.png");
|
--button-background-image-mobile: url("/assets/buttons/button-fade.png");
|
||||||
--button-background-color-mobile: #f0e0d6;
|
--button-background-color-mobile: #f0e0d6;
|
||||||
--button-border-mobile: 1px solid #c0a69d;
|
--button-border-mobile: 1px solid #c0a69d;
|
||||||
|
|
||||||
@@ -506,8 +506,8 @@
|
|||||||
--post-capcode-mod-text-color: purple;
|
--post-capcode-mod-text-color: purple;
|
||||||
|
|
||||||
/* post desktop */
|
/* post desktop */
|
||||||
--post-hide-button-background-image: url("/public/assets/buttons/minus-red.png");
|
--post-hide-button-background-image: url("/assets/buttons/minus-red.png");
|
||||||
--post-unhide-button-background-image: url("/public/assets/buttons/plus-red.png");
|
--post-unhide-button-background-image: url("/assets/buttons/plus-red.png");
|
||||||
--post-subject-text-color: #cc1105;
|
--post-subject-text-color: #cc1105;
|
||||||
--post-subject-font-weight: 700;
|
--post-subject-font-weight: 700;
|
||||||
--post-name-text-color: #117743;
|
--post-name-text-color: #117743;
|
||||||
@@ -583,9 +583,9 @@
|
|||||||
--settings-modal-border-bottom: 1px solid rgba(0, 0, 0, 0.20);
|
--settings-modal-border-bottom: 1px solid rgba(0, 0, 0, 0.20);
|
||||||
--settings-modal-border-left: 1px solid rgba(0, 0, 0, 0.20);
|
--settings-modal-border-left: 1px solid rgba(0, 0, 0, 0.20);
|
||||||
--settings-modal-header-border-bottom: 1px solid rgba(0,0,0,.2);
|
--settings-modal-header-border-bottom: 1px solid rgba(0,0,0,.2);
|
||||||
--settings-modal-close-button-background-image: url("/public/assets/buttons/cross-red.png");
|
--settings-modal-close-button-background-image: url("/assets/buttons/cross-red.png");
|
||||||
--settings-modal-show-button-background-image: url("/public/assets/buttons/plus-red.png");
|
--settings-modal-show-button-background-image: url("/assets/buttons/plus-red.png");
|
||||||
--settings-modal-hide-button-background-image: url("/public/assets/buttons/minus-red.png");
|
--settings-modal-hide-button-background-image: url("/assets/buttons/minus-red.png");
|
||||||
|
|
||||||
/* stats */
|
/* stats */
|
||||||
--stats-font-size: 10pt;
|
--stats-font-size: 10pt;
|
||||||
@@ -656,7 +656,7 @@
|
|||||||
--button-desktop-text-color: #34345c;
|
--button-desktop-text-color: #34345c;
|
||||||
--button-text-decoration: underline;
|
--button-text-decoration: underline;
|
||||||
--button-desktop-text-color-hover: #d00;
|
--button-desktop-text-color-hover: #d00;
|
||||||
--close-button-background-image: url("/public/assets/buttons/cross-blue.png");
|
--close-button-background-image: url("/assets/buttons/cross-blue.png");
|
||||||
|
|
||||||
/* footer */
|
/* footer */
|
||||||
--footer-item-background-color-desktop: #eef2ff;
|
--footer-item-background-color-desktop: #eef2ff;
|
||||||
@@ -678,7 +678,7 @@
|
|||||||
--button-font-size-mobile: 11pt;
|
--button-font-size-mobile: 11pt;
|
||||||
--button-font-weight-mobile: 700;
|
--button-font-weight-mobile: 700;
|
||||||
--button-text-color-mobile: #34345c;
|
--button-text-color-mobile: #34345c;
|
||||||
--button-background-image-mobile: url("/public/assets/buttons/button-fade-blue.png");
|
--button-background-image-mobile: url("/assets/buttons/button-fade-blue.png");
|
||||||
--button-background-color-mobile: #d6daf0;
|
--button-background-color-mobile: #d6daf0;
|
||||||
--button-border-mobile: 1px solid #b7c5d9;
|
--button-border-mobile: 1px solid #b7c5d9;
|
||||||
|
|
||||||
@@ -694,8 +694,8 @@
|
|||||||
--post-capcode-mod-text-color: purple;
|
--post-capcode-mod-text-color: purple;
|
||||||
|
|
||||||
/* post desktop */
|
/* post desktop */
|
||||||
--post-hide-button-background-image: url("/public/assets/buttons/minus-blue.png");
|
--post-hide-button-background-image: url("/assets/buttons/minus-blue.png");
|
||||||
--post-unhide-button-background-image: url("/public/assets/buttons/plus-blue.png");
|
--post-unhide-button-background-image: url("/assets/buttons/plus-blue.png");
|
||||||
--post-subject-text-color: #0f0c5d;
|
--post-subject-text-color: #0f0c5d;
|
||||||
--post-subject-font-weight: 700;
|
--post-subject-font-weight: 700;
|
||||||
--post-name-text-color: #117743;
|
--post-name-text-color: #117743;
|
||||||
@@ -771,9 +771,9 @@
|
|||||||
--settings-modal-border-bottom: 1px solid rgba(0, 0, 0, 0.20);
|
--settings-modal-border-bottom: 1px solid rgba(0, 0, 0, 0.20);
|
||||||
--settings-modal-border-left: 1px solid rgba(0, 0, 0, 0.20);
|
--settings-modal-border-left: 1px solid rgba(0, 0, 0, 0.20);
|
||||||
--settings-modal-header-border-bottom: 1px solid rgba(0,0,0,.2);
|
--settings-modal-header-border-bottom: 1px solid rgba(0,0,0,.2);
|
||||||
--settings-modal-close-button-background-image: url("/public/assets/buttons/cross-blue.png");
|
--settings-modal-close-button-background-image: url("/assets/buttons/cross-blue.png");
|
||||||
--settings-modal-show-button-background-image: url("/public/assets/buttons/plus-blue.png");
|
--settings-modal-show-button-background-image: url("/assets/buttons/plus-blue.png");
|
||||||
--settings-modal-hide-button-background-image: url("/public/assets/buttons/minus-blue.png");
|
--settings-modal-hide-button-background-image: url("/assets/buttons/minus-blue.png");
|
||||||
|
|
||||||
/* stats */
|
/* stats */
|
||||||
--stats-font-size: 10pt;
|
--stats-font-size: 10pt;
|
||||||
@@ -847,7 +847,7 @@
|
|||||||
/* desktop buttons */
|
/* desktop buttons */
|
||||||
--button-desktop-text-color: #81a2be;
|
--button-desktop-text-color: #81a2be;
|
||||||
--button-desktop-text-color-hover: #5f89ac;
|
--button-desktop-text-color-hover: #5f89ac;
|
||||||
--close-button-background-image: url("/public/assets/buttons/cross-dark.png");
|
--close-button-background-image: url("/assets/buttons/cross-dark.png");
|
||||||
|
|
||||||
/* filter for inputs */
|
/* filter for inputs */
|
||||||
--filter80: brightness(85%);
|
--filter80: brightness(85%);
|
||||||
@@ -882,7 +882,7 @@
|
|||||||
--button-font-size-mobile: 10pt;
|
--button-font-size-mobile: 10pt;
|
||||||
--button-font-weight-mobile: 700;
|
--button-font-weight-mobile: 700;
|
||||||
--button-text-color-mobile: #707070;
|
--button-text-color-mobile: #707070;
|
||||||
--button-background-image-mobile: url("/public/assets/buttons/button-fade-dark.png");
|
--button-background-image-mobile: url("/assets/buttons/button-fade-dark.png");
|
||||||
--button-background-color-mobile: #1b1c1e;
|
--button-background-color-mobile: #1b1c1e;
|
||||||
--button-border-mobile: 1px solid #282a2e;
|
--button-border-mobile: 1px solid #282a2e;
|
||||||
|
|
||||||
@@ -890,8 +890,8 @@
|
|||||||
--post-capcode-mod-text-color: #81a2be;
|
--post-capcode-mod-text-color: #81a2be;
|
||||||
|
|
||||||
/* post desktop */
|
/* post desktop */
|
||||||
--post-hide-button-background-image: url("/public/assets/buttons/minus-dark.png");
|
--post-hide-button-background-image: url("/assets/buttons/minus-dark.png");
|
||||||
--post-unhide-button-background-image: url("/public/assets/buttons/plus-dark.png");
|
--post-unhide-button-background-image: url("/assets/buttons/plus-dark.png");
|
||||||
--post-subject-text-color: #b294bb;
|
--post-subject-text-color: #b294bb;
|
||||||
--post-subject-font-weight: 700;
|
--post-subject-font-weight: 700;
|
||||||
--post-name-text-color: #c5c8c6;
|
--post-name-text-color: #c5c8c6;
|
||||||
@@ -993,9 +993,9 @@
|
|||||||
--settings-modal-border-bottom: 1px solid #111;
|
--settings-modal-border-bottom: 1px solid #111;
|
||||||
--settings-modal-border-left: 1px solid #111;
|
--settings-modal-border-left: 1px solid #111;
|
||||||
--settings-modal-header-border-bottom: 1px solid #111;
|
--settings-modal-header-border-bottom: 1px solid #111;
|
||||||
--settings-modal-close-button-background-image: url("/public/assets/buttons/cross-dark.png");
|
--settings-modal-close-button-background-image: url("/assets/buttons/cross-dark.png");
|
||||||
--settings-modal-show-button-background-image: url("/public/assets/buttons/plus-dark.png");
|
--settings-modal-show-button-background-image: url("/assets/buttons/plus-dark.png");
|
||||||
--settings-modal-hide-button-background-image: url("/public/assets/buttons/minus-dark.png");
|
--settings-modal-hide-button-background-image: url("/assets/buttons/minus-dark.png");
|
||||||
|
|
||||||
/* stats */
|
/* stats */
|
||||||
--stats-font-size: 11px;
|
--stats-font-size: 11px;
|
||||||
@@ -1066,7 +1066,7 @@
|
|||||||
/* desktop buttons */
|
/* desktop buttons */
|
||||||
--button-desktop-text-color: #f60;
|
--button-desktop-text-color: #f60;
|
||||||
--button-desktop-text-color-hover: #f30;
|
--button-desktop-text-color-hover: #f30;
|
||||||
--close-button-background-image: url("/public/assets/buttons/cross-photon.png");
|
--close-button-background-image: url("/assets/buttons/cross-photon.png");
|
||||||
|
|
||||||
/* horizontal rule */
|
/* horizontal rule */
|
||||||
--hr-border: none;
|
--hr-border: none;
|
||||||
@@ -1095,8 +1095,8 @@
|
|||||||
--post-capcode-mod-text-color: #f60;
|
--post-capcode-mod-text-color: #f60;
|
||||||
|
|
||||||
/* post desktop */
|
/* post desktop */
|
||||||
--post-hide-button-background-image: url("/public/assets/buttons/minus-photon.png");
|
--post-hide-button-background-image: url("/assets/buttons/minus-photon.png");
|
||||||
--post-unhide-button-background-image: url("/public/assets/buttons/plus-photon.png");
|
--post-unhide-button-background-image: url("/assets/buttons/plus-photon.png");
|
||||||
--post-subject-text-color: #111;
|
--post-subject-text-color: #111;
|
||||||
--post-subject-font-weight: 700;
|
--post-subject-font-weight: 700;
|
||||||
--post-name-text-color: #004a99;
|
--post-name-text-color: #004a99;
|
||||||
@@ -1196,9 +1196,9 @@
|
|||||||
--settings-modal-border-bottom: 1px solid #ccc;
|
--settings-modal-border-bottom: 1px solid #ccc;
|
||||||
--settings-modal-border-left: 1px solid #ccc;
|
--settings-modal-border-left: 1px solid #ccc;
|
||||||
--settings-modal-header-border-bottom: 1px solid rgba(0, 0, 0, 0.20);
|
--settings-modal-header-border-bottom: 1px solid rgba(0, 0, 0, 0.20);
|
||||||
--settings-modal-close-button-background-image: url("/public/assets/buttons/cross-photon.png");
|
--settings-modal-close-button-background-image: url("/assets/buttons/cross-photon.png");
|
||||||
--settings-modal-show-button-background-image: url("/public/assets/buttons/plus-photon.png");
|
--settings-modal-show-button-background-image: url("/assets/buttons/plus-photon.png");
|
||||||
--settings-modal-hide-button-background-image: url("/public/assets/buttons/minus-photon.png");
|
--settings-modal-hide-button-background-image: url("/assets/buttons/minus-photon.png");
|
||||||
|
|
||||||
/* stats */
|
/* stats */
|
||||||
--stats-font-size: 11px;
|
--stats-font-size: 11px;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
border-image-width: 40px 0px 0px 0px;
|
border-image-width: 40px 0px 0px 0px;
|
||||||
border-image-outset: 0px 0px 0px 0px;
|
border-image-outset: 0px 0px 0px 0px;
|
||||||
border-image-repeat: repeat repeat;
|
border-image-repeat: repeat repeat;
|
||||||
border-image-source: url('/public/assets/garland.png');
|
border-image-source: url('/assets/garland.png');
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
}
|
}
|
||||||
+26
-32
@@ -1,30 +1,28 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment, useAccount, useAccountComments, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAccount, useAccountComments, useAccountSubplebbits, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import styles from './board.module.css';
|
import styles from './board.module.css';
|
||||||
import { shouldShowSnow } from '../../lib/snow';
|
import { shouldShowSnow } from '../../lib/snow';
|
||||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
|
||||||
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||||
import { useFeedStateString } from '../../hooks/use-state-string';
|
import { useFeedStateString } from '../../hooks/use-state-string';
|
||||||
import useReplyModal from '../../hooks/use-reply-modal';
|
|
||||||
import useTimeFilter from '../../hooks/use-time-filter';
|
import useTimeFilter from '../../hooks/use-time-filter';
|
||||||
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
|
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
|
||||||
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||||
import useSortingStore from '../../stores/use-sorting-store';
|
import useSortingStore from '../../stores/use-sorting-store';
|
||||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||||
import { Post } from '../post';
|
|
||||||
import ReplyModal from '../../components/reply-modal';
|
|
||||||
import SettingsModal from '../../components/settings-modal';
|
|
||||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
import SubplebbitDescription from '../../components/subplebbit-description';
|
||||||
import SubplebbitRules from '../../components/subplebbit-rules';
|
import SubplebbitRules from '../../components/subplebbit-rules';
|
||||||
|
import { Post } from '../post';
|
||||||
|
|
||||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||||
|
|
||||||
const threadsWithoutImagesFilter = (comment: Comment) => {
|
const threadsWithoutImagesFilter = (comment: Comment) => {
|
||||||
if (!getHasThumbnail(getCommentMediaInfo(comment), comment?.link)) {
|
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {};
|
||||||
|
if (!getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -43,6 +41,10 @@ const Board = () => {
|
|||||||
const subscriptions = account?.subscriptions;
|
const subscriptions = account?.subscriptions;
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
||||||
|
|
||||||
|
const isInModView = isModView(location.pathname);
|
||||||
|
const { accountSubplebbits } = useAccountSubplebbits();
|
||||||
|
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
||||||
|
|
||||||
const subplebbitAddresses = useMemo(() => {
|
const subplebbitAddresses = useMemo(() => {
|
||||||
if (isInAllView) {
|
if (isInAllView) {
|
||||||
return defaultSubplebbitAddresses;
|
return defaultSubplebbitAddresses;
|
||||||
@@ -50,8 +52,11 @@ const Board = () => {
|
|||||||
if (isInSubscriptionsView) {
|
if (isInSubscriptionsView) {
|
||||||
return subscriptions || [];
|
return subscriptions || [];
|
||||||
}
|
}
|
||||||
|
if (isInModView) {
|
||||||
|
return accountSubplebbitAddresses;
|
||||||
|
}
|
||||||
return [subplebbitAddress];
|
return [subplebbitAddress];
|
||||||
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions]);
|
}, [isInAllView, isInSubscriptionsView, isInModView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions, accountSubplebbitAddresses]);
|
||||||
|
|
||||||
const { sortType } = useSortingStore();
|
const { sortType } = useSortingStore();
|
||||||
const { timeFilterSeconds, timeFilterName } = useTimeFilter();
|
const { timeFilterSeconds, timeFilterName } = useTimeFilter();
|
||||||
@@ -59,8 +64,8 @@ const Board = () => {
|
|||||||
const feedOptions = {
|
const feedOptions = {
|
||||||
subplebbitAddresses,
|
subplebbitAddresses,
|
||||||
sortType,
|
sortType,
|
||||||
postsPerPage: isInAllView || isInSubscriptionsView ? 5 : 25,
|
postsPerPage: isInAllView || isInSubscriptionsView || isInModView ? 5 : 25,
|
||||||
...(isInAllView || isInSubscriptionsView ? { newerThan: timeFilterSeconds } : {}),
|
...(isInAllView || isInSubscriptionsView || isInModView ? { newerThan: timeFilterSeconds } : {}),
|
||||||
filter: hideThreadsWithoutImages ? threadsWithoutImagesFilter : undefined,
|
filter: hideThreadsWithoutImages ? threadsWithoutImagesFilter : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,14 +83,14 @@ const Board = () => {
|
|||||||
const filteredComments = useMemo(
|
const filteredComments = useMemo(
|
||||||
() =>
|
() =>
|
||||||
accountComments.filter((comment) => {
|
accountComments.filter((comment) => {
|
||||||
const { cid, deleted, postCid, removed, state, timestamp } = comment || {};
|
const { cid, deleted, link, linkHeight, linkWidth, postCid, removed, state, thumbnailUrl, timestamp } = comment || {};
|
||||||
return (
|
return (
|
||||||
!deleted &&
|
!deleted &&
|
||||||
!removed &&
|
!removed &&
|
||||||
timestamp > Date.now() / 1000 - 60 * 60 &&
|
timestamp > Date.now() / 1000 - 60 * 60 &&
|
||||||
state === 'succeeded' &&
|
state === 'succeeded' &&
|
||||||
cid &&
|
cid &&
|
||||||
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(comment), comment?.link) : true) &&
|
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), comment?.link) : true) &&
|
||||||
cid === postCid &&
|
cid === postCid &&
|
||||||
comment?.subplebbitAddress === subplebbitAddress &&
|
comment?.subplebbitAddress === subplebbitAddress &&
|
||||||
!feed.some((post) => post.cid === cid)
|
!feed.some((post) => post.cid === cid)
|
||||||
@@ -113,9 +118,7 @@ const Board = () => {
|
|||||||
|
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||||
const { createdAt, description, error, rules, shortAddress, state, suggested } = subplebbit || {};
|
const { createdAt, description, error, rules, shortAddress, state, suggested } = subplebbit || {};
|
||||||
const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : subplebbit?.title;
|
const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : isInModView ? t('mod') : subplebbit?.title;
|
||||||
|
|
||||||
const { activeCid, threadCid, closeModal, openReplyModal, showReplyModal, scrollY, subplebbitAddress: postSubplebbitAddress } = useReplyModal();
|
|
||||||
|
|
||||||
const { blocked, unblock } = useBlock({ address: subplebbitAddress });
|
const { blocked, unblock } = useBlock({ address: subplebbitAddress });
|
||||||
|
|
||||||
@@ -178,7 +181,7 @@ const Board = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
(isInAllView || isInSubscriptionsView) &&
|
(isInAllView || isInSubscriptionsView || isInModView) &&
|
||||||
showMorePostsSuggestion &&
|
showMorePostsSuggestion &&
|
||||||
monthlyFeed.length > feed.length &&
|
monthlyFeed.length > feed.length &&
|
||||||
(weeklyFeed.length > feed.length ? (
|
(weeklyFeed.length > feed.length ? (
|
||||||
@@ -187,7 +190,7 @@ const Board = () => {
|
|||||||
i18nKey='more_threads_last_week'
|
i18nKey='more_threads_last_week'
|
||||||
values={{ currentTimeFilterName, count: feed.length }}
|
values={{ currentTimeFilterName, count: feed.length }}
|
||||||
components={{
|
components={{
|
||||||
1: <Link to={(isInAllView ? '/p/all' : isInSubscriptionsView ? '/p/subscriptions' : `/p/${subplebbitAddress}`) + '/1w'} />,
|
1: <Link to={(isInAllView ? '/p/all' : isInSubscriptionsView ? '/p/subscriptions' : isInModView ? '/p/mod' : `/p/${subplebbitAddress}`) + '/1w'} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,7 +200,7 @@ const Board = () => {
|
|||||||
i18nKey='more_threads_last_month'
|
i18nKey='more_threads_last_month'
|
||||||
values={{ currentTimeFilterName, count: feed.length }}
|
values={{ currentTimeFilterName, count: feed.length }}
|
||||||
components={{
|
components={{
|
||||||
1: <Link to={(isInAllView ? '/p/all' : isInSubscriptionsView ? '/p/subscriptions' : `/p/${subplebbitAddress}`) + '/1m'} />,
|
1: <Link to={(isInAllView ? '/p/all' : isInSubscriptionsView ? '/p/subscriptions' : isInModView ? '/p/mod' : `/p/${subplebbitAddress}`) + '/1m'} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -213,9 +216,11 @@ const Board = () => {
|
|||||||
{state === 'failed' ? (
|
{state === 'failed' ? (
|
||||||
<span className='red'>{state}</span>
|
<span className='red'>{state}</span>
|
||||||
) : isInSubscriptionsView && subscriptions?.length === 0 ? (
|
) : isInSubscriptionsView && subscriptions?.length === 0 ? (
|
||||||
t('not_subscribed_to_any_board')
|
<span className='red'>{t('not_subscribed_to_any_board')}</span>
|
||||||
|
) : isInModView && accountSubplebbitAddresses?.length === 0 ? (
|
||||||
|
<span className='red'>{t('not_mod_of_any_board')}</span>
|
||||||
) : blocked ? (
|
) : blocked ? (
|
||||||
t('you_have_blocked_this_board')
|
<span className='red'>{t('you_have_blocked_this_board')}</span>
|
||||||
) : (
|
) : (
|
||||||
hasMore && <LoadingEllipsis string={loadingStateString} />
|
hasMore && <LoadingEllipsis string={loadingStateString} />
|
||||||
)}
|
)}
|
||||||
@@ -270,17 +275,6 @@ const Board = () => {
|
|||||||
<>
|
<>
|
||||||
{shouldShowSnow() && <hr />}
|
{shouldShowSnow() && <hr />}
|
||||||
<div className={`${styles.content} ${shouldShowSnow() ? styles.garland : ''}`}>
|
<div className={`${styles.content} ${shouldShowSnow() ? styles.garland : ''}`}>
|
||||||
{location.pathname.endsWith('/settings') && <SettingsModal />}
|
|
||||||
{activeCid && threadCid && postSubplebbitAddress && (
|
|
||||||
<ReplyModal
|
|
||||||
closeModal={closeModal}
|
|
||||||
parentCid={activeCid}
|
|
||||||
postCid={threadCid}
|
|
||||||
scrollY={scrollY}
|
|
||||||
showReplyModal={showReplyModal}
|
|
||||||
subplebbitAddress={postSubplebbitAddress}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{((description && description.length > 0) || isInAllView) && (
|
{((description && description.length > 0) || isInAllView) && (
|
||||||
<SubplebbitDescription
|
<SubplebbitDescription
|
||||||
avatarUrl={suggested?.avatarUrl}
|
avatarUrl={suggested?.avatarUrl}
|
||||||
@@ -297,7 +291,7 @@ const Board = () => {
|
|||||||
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
||||||
totalCount={combinedFeed.length}
|
totalCount={combinedFeed.length}
|
||||||
data={combinedFeed}
|
data={combinedFeed}
|
||||||
itemContent={(index, post) => <Post index={index} post={post} openReplyModal={openReplyModal} />}
|
itemContent={(index, post) => <Post index={index} post={post} />}
|
||||||
useWindowScroll={true}
|
useWindowScroll={true}
|
||||||
components={{ Footer }}
|
components={{ Footer }}
|
||||||
endReached={loadMore}
|
endReached={loadMore}
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import useInterfaceSettingsStore from '../../stores/use-interface-settings-store
|
|||||||
import useSortingStore from '../../stores/use-sorting-store';
|
import useSortingStore from '../../stores/use-sorting-store';
|
||||||
import CatalogRow from '../../components/catalog-row';
|
import CatalogRow from '../../components/catalog-row';
|
||||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||||
import SettingsModal from '../../components/settings-modal';
|
|
||||||
import styles from './catalog.module.css';
|
import styles from './catalog.module.css';
|
||||||
|
|
||||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||||
|
|
||||||
const threadsWithoutImagesFilter = (comment: Comment) => {
|
const threadsWithoutImagesFilter = (comment: Comment) => {
|
||||||
if (!getHasThumbnail(getCommentMediaInfo(comment), comment?.link)) {
|
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {};
|
||||||
|
if (!getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -98,14 +98,14 @@ const Catalog = () => {
|
|||||||
const filteredComments = useMemo(
|
const filteredComments = useMemo(
|
||||||
() =>
|
() =>
|
||||||
accountComments.filter((comment) => {
|
accountComments.filter((comment) => {
|
||||||
const { cid, deleted, postCid, removed, state, timestamp } = comment || {};
|
const { cid, deleted, link, linkHeight, linkWidth, postCid, removed, state, thumbnailUrl, timestamp } = comment || {};
|
||||||
return (
|
return (
|
||||||
!deleted &&
|
!deleted &&
|
||||||
!removed &&
|
!removed &&
|
||||||
timestamp > Date.now() / 1000 - 60 * 60 &&
|
timestamp > Date.now() / 1000 - 60 * 60 &&
|
||||||
state === 'succeeded' &&
|
state === 'succeeded' &&
|
||||||
cid &&
|
cid &&
|
||||||
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(comment), comment?.link) : true) &&
|
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), comment?.link) : true) &&
|
||||||
cid === postCid &&
|
cid === postCid &&
|
||||||
comment?.subplebbitAddress === subplebbitAddress &&
|
comment?.subplebbitAddress === subplebbitAddress &&
|
||||||
!feed.some((post) => post.cid === cid)
|
!feed.some((post) => post.cid === cid)
|
||||||
@@ -288,7 +288,6 @@ const Catalog = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{location.pathname.endsWith('/settings') && <SettingsModal />}
|
|
||||||
<hr />
|
<hr />
|
||||||
<div className={styles.catalog}>
|
<div className={styles.catalog}>
|
||||||
{combinedFeed.length !== 0 ? (
|
{combinedFeed.length !== 0 ? (
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import { useState, useEffect } from 'react';
|
|||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||||
import { Subplebbit, useSubplebbit, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
|
import { Subplebbit, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import { useDefaultSubplebbitsState, useDefaultSubplebbitTags } from '../../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbitsState, useDefaultSubplebbitTags } from '../../../hooks/use-default-subplebbits';
|
||||||
import useIsMobile from '../../../hooks/use-is-mobile';
|
import useIsMobile from '../../../hooks/use-is-mobile';
|
||||||
import useIsSubplebbitOffline from '../../../hooks/use-is-subplebbit-offline';
|
import useIsSubplebbitOffline from '../../../hooks/use-is-subplebbit-offline';
|
||||||
import LoadingEllipsis from '../../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../../components/loading-ellipsis';
|
||||||
import Tooltip from '../../../components/tooltip';
|
import Tooltip from '../../../components/tooltip';
|
||||||
import styles from './boards-list.module.css';
|
import styles from './boards-list.module.css';
|
||||||
import { nsfwTags } from '../home';
|
import { nsfwTags } from '../../../constants/nsfwTags';
|
||||||
|
|
||||||
const Board = ({ subplebbit, isMobile }: { subplebbit: Subplebbit; isMobile: boolean }) => {
|
const Board = ({ subplebbit, isMobile }: { subplebbit: Subplebbit; isMobile: boolean }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -18,7 +19,7 @@ const Board = ({ subplebbit, isMobile }: { subplebbit: Subplebbit; isMobile: boo
|
|||||||
|
|
||||||
let stats = useSubplebbitStats({ subplebbitAddress: address });
|
let stats = useSubplebbitStats({ subplebbitAddress: address });
|
||||||
|
|
||||||
const subplebbitData = useSubplebbit({ subplebbitAddress: address });
|
const subplebbitData = useSubplebbitsStore((state) => state.subplebbits[address]);
|
||||||
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
||||||
|
|
||||||
const displayAddress = address && Plebbit.getShortAddress(address);
|
const displayAddress = address && Plebbit.getShortAddress(address);
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import Version from '../../components/version';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// https://github.com/plebbit/temporary-default-subplebbits/blob/master/README.md
|
// https://github.com/plebbit/temporary-default-subplebbits/blob/master/README.md
|
||||||
// plebchan shouldn't consider 'vulgar' or 'anti' as nsfw tags, unlike more sfw-oriented clients
|
|
||||||
export const nsfwTags = ['adult', 'gore'];
|
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = () => {
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -52,7 +50,13 @@ const InfoBox = () => {
|
|||||||
<h2>{t('what_is_plebchan')}</h2>
|
<h2>{t('what_is_plebchan')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.boxContent}>
|
<div className={styles.boxContent}>
|
||||||
<Trans i18nKey='plebchan_description' shouldUnescape={true} components={{ 1: <Link to='https://plebbit.com' target='_blank' rel='noopener noreferrer' /> }} />
|
<Trans
|
||||||
|
i18nKey='plebchan_description'
|
||||||
|
shouldUnescape={true}
|
||||||
|
components={{
|
||||||
|
1: <Link key='plebbit-link' to='https://plebbit.com' target='_blank' rel='noopener noreferrer' />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{t('no_global_rules_info')}
|
{t('no_global_rules_info')}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { HomeLogo, Footer, nsfwTags, default } from './home';
|
export { HomeLogo, Footer, default } from './home';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
|
|||||||
import { CatalogPostMedia } from '../../../components/catalog-row';
|
import { CatalogPostMedia } from '../../../components/catalog-row';
|
||||||
import LoadingEllipsis from '../../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../../components/loading-ellipsis';
|
||||||
import BoxModal from '../box-modal';
|
import BoxModal from '../box-modal';
|
||||||
import { nsfwTags } from '../home';
|
import { nsfwTags } from '../../../constants/nsfwTags';
|
||||||
import { removeMarkdown } from '../../../lib/utils/post-utils';
|
import { removeMarkdown } from '../../../lib/utils/post-utils';
|
||||||
|
|
||||||
interface PopularThreadProps {
|
interface PopularThreadProps {
|
||||||
@@ -26,8 +26,8 @@ export const ContentPreview = ({ content, maxLength = 99 }: { content: string; m
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
|
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
|
||||||
const { cid, content, subplebbitAddress, title } = post || {};
|
const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {};
|
||||||
const commentMediaInfo = getCommentMediaInfo(post);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.popularThread} key={cid}>
|
<div className={styles.popularThread} key={cid}>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import { HomeLogo } from '../home';
|
import { HomeLogo } from '../home';
|
||||||
import styles from './not-found.module.css';
|
import styles from './not-found.module.css';
|
||||||
import { useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
|
||||||
|
|
||||||
const totalNotFoundImages = 2;
|
const totalNotFoundImages = 2;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ const NotFoundImage = () => {
|
|||||||
const NotFound = () => {
|
const NotFound = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const subplebbitAddress = location.pathname.startsWith('/p/') ? location.pathname.split('/')[2] : '';
|
const subplebbitAddress = location.pathname.startsWith('/p/') ? location.pathname.split('/')[2] : '';
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
||||||
const { address, shortAddress } = subplebbit || {};
|
const { address, shortAddress } = subplebbit || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useAccountComment, useAccountComments } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment, useAccountComments } from '@plebbit/plebbit-react-hooks';
|
||||||
import { isSettingsView } from '../../lib/utils/view-utils';
|
|
||||||
import { Post } from '../post';
|
import { Post } from '../post';
|
||||||
import SettingsModal from '../../components/settings-modal';
|
|
||||||
|
|
||||||
const PendingPost = () => {
|
const PendingPost = () => {
|
||||||
const { accountComments } = useAccountComments();
|
const { accountComments } = useAccountComments();
|
||||||
@@ -11,9 +9,6 @@ const PendingPost = () => {
|
|||||||
const commentIndex = accountCommentIndex ? parseInt(accountCommentIndex) : undefined;
|
const commentIndex = accountCommentIndex ? parseInt(accountCommentIndex) : undefined;
|
||||||
const post = useAccountComment({ commentIndex });
|
const post = useAccountComment({ commentIndex });
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
|
||||||
const params = useParams();
|
|
||||||
const isInSettingsView = isSettingsView(location.pathname, params);
|
|
||||||
|
|
||||||
useEffect(() => window.scrollTo(0, 0), []);
|
useEffect(() => window.scrollTo(0, 0), []);
|
||||||
|
|
||||||
@@ -36,12 +31,7 @@ const PendingPost = () => {
|
|||||||
}
|
}
|
||||||
}, [post, navigate]);
|
}, [post, navigate]);
|
||||||
|
|
||||||
return (
|
return <Post post={post} />;
|
||||||
<>
|
|
||||||
{isInSettingsView && <SettingsModal />}
|
|
||||||
<Post post={post} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PendingPost;
|
export default PendingPost;
|
||||||
|
|||||||
+8
-25
@@ -1,18 +1,15 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { isAllView, isDescriptionView, isRulesView, isSettingsView } from '../../lib/utils/view-utils';
|
import { isAllView, isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
|
||||||
import useIsMobile from '../../hooks/use-is-mobile';
|
import useIsMobile from '../../hooks/use-is-mobile';
|
||||||
import useReplyModal from '../../hooks/use-reply-modal';
|
|
||||||
import PostDesktop from '../../components/post-desktop';
|
import PostDesktop from '../../components/post-desktop';
|
||||||
import PostMobile from '../../components/post-mobile';
|
import PostMobile from '../../components/post-mobile';
|
||||||
import ReplyModal from '../../components/reply-modal';
|
|
||||||
import SettingsModal from '../../components/settings-modal';
|
|
||||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
import SubplebbitDescription from '../../components/subplebbit-description';
|
||||||
import SubplebbitRules from '../../components/subplebbit-rules';
|
import SubplebbitRules from '../../components/subplebbit-rules';
|
||||||
import styles from './post.module.css';
|
import styles from './post.module.css';
|
||||||
|
|
||||||
export interface PostProps {
|
export interface PostProps {
|
||||||
index?: number;
|
index?: number;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
@@ -23,11 +20,11 @@ export interface PostProps {
|
|||||||
roles?: Role[];
|
roles?: Role[];
|
||||||
showAllReplies?: boolean;
|
showAllReplies?: boolean;
|
||||||
showReplies?: boolean;
|
showReplies?: boolean;
|
||||||
openReplyModal?: (parentCid: string, postCid: string, subplebbitAddress: string) => void;
|
replies?: Comment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Post = ({ post, showAllReplies = false, showReplies = true, openReplyModal }: PostProps) => {
|
export const Post = ({ post, showAllReplies = false, showReplies = true, replies }: PostProps) => {
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress: post?.subplebbitAddress });
|
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[post?.subplebbitAddress]);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
let comment = post;
|
let comment = post;
|
||||||
@@ -42,9 +39,9 @@ export const Post = ({ post, showAllReplies = false, showReplies = true, openRep
|
|||||||
<div className={styles.thread}>
|
<div className={styles.thread}>
|
||||||
<div className={styles.postContainer}>
|
<div className={styles.postContainer}>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<PostMobile post={comment} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} openReplyModal={openReplyModal} />
|
<PostMobile post={comment} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} replies={replies} />
|
||||||
) : (
|
) : (
|
||||||
<PostDesktop post={comment} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} openReplyModal={openReplyModal} />
|
<PostDesktop post={comment} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} replies={replies} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,15 +54,12 @@ const PostPage = () => {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { commentCid, subplebbitAddress } = params;
|
const { commentCid, subplebbitAddress } = params;
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const isInSettigsView = isSettingsView(location.pathname, params);
|
|
||||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
||||||
const isInRulesView = isRulesView(location.pathname, params);
|
const isInRulesView = isRulesView(location.pathname, params);
|
||||||
|
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||||
const { createdAt, description, rules, shortAddress, suggested, title } = subplebbit;
|
const { createdAt, description, rules, shortAddress, suggested, title } = subplebbit;
|
||||||
|
|
||||||
const { activeCid, threadCid, closeModal, openReplyModal, showReplyModal, scrollY, subplebbitAddress: postSubplebbitAddress } = useReplyModal();
|
|
||||||
|
|
||||||
const comment = useComment({ commentCid });
|
const comment = useComment({ commentCid });
|
||||||
|
|
||||||
// if the comment is a reply, return the post comment instead, then the reply will be highlighted in the thread
|
// if the comment is a reply, return the post comment instead, then the reply will be highlighted in the thread
|
||||||
@@ -92,17 +86,6 @@ const PostPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{isInSettigsView && <SettingsModal />}
|
|
||||||
{activeCid && threadCid && postSubplebbitAddress && (
|
|
||||||
<ReplyModal
|
|
||||||
closeModal={closeModal}
|
|
||||||
parentCid={activeCid}
|
|
||||||
postCid={threadCid}
|
|
||||||
scrollY={scrollY}
|
|
||||||
showReplyModal={showReplyModal}
|
|
||||||
subplebbitAddress={postSubplebbitAddress}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* TODO: remove this replyCount error once api supports scrolling replies pages */}
|
{/* TODO: remove this replyCount error once api supports scrolling replies pages */}
|
||||||
{replyCount > 60 && <span className={styles.error}>Error: this thread has too many replies, some of them cannot be displayed right now.</span>}
|
{replyCount > 60 && <span className={styles.error}>Error: this thread has too many replies, some of them cannot be displayed right now.</span>}
|
||||||
{error && <span className={styles.error}>Error: {error?.message || error?.toString?.()}</span>}
|
{error && <span className={styles.error}>Error: {error?.message || error?.toString?.()}</span>}
|
||||||
@@ -119,7 +102,7 @@ const PostPage = () => {
|
|||||||
) : isInRulesView ? (
|
) : isInRulesView ? (
|
||||||
<SubplebbitRules createdAt={createdAt} rules={rules} subplebbitAddress={subplebbitAddress} />
|
<SubplebbitRules createdAt={createdAt} rules={rules} subplebbitAddress={subplebbitAddress} />
|
||||||
) : (
|
) : (
|
||||||
<Post post={post} showAllReplies={true} openReplyModal={openReplyModal} />
|
<Post post={post} showAllReplies={true} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||||
|
import eslint from 'vite-plugin-eslint';
|
||||||
|
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react({
|
||||||
|
babel: {
|
||||||
|
plugins: [
|
||||||
|
['babel-plugin-react-compiler', {
|
||||||
|
verbose: true
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
!isProduction && eslint({
|
||||||
|
lintOnStart: true,
|
||||||
|
overrideConfigFile: './.eslintrc.cjs',
|
||||||
|
failOnError: false,
|
||||||
|
failOnWarning: false,
|
||||||
|
cache: true,
|
||||||
|
}),
|
||||||
|
nodePolyfills({
|
||||||
|
globals: {
|
||||||
|
Buffer: true,
|
||||||
|
global: true,
|
||||||
|
process: true,
|
||||||
|
},
|
||||||
|
protocolImports: true,
|
||||||
|
include: ['crypto', 'stream', 'util', 'buffer', 'events'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, 'src'),
|
||||||
|
'node-fetch': 'isomorphic-fetch',
|
||||||
|
'assert': 'assert',
|
||||||
|
'stream': 'stream-browserify',
|
||||||
|
'crypto': 'crypto-browserify',
|
||||||
|
'buffer': 'buffer',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
open: true,
|
||||||
|
watch: {
|
||||||
|
usePolling: true,
|
||||||
|
},
|
||||||
|
hmr: {
|
||||||
|
overlay: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'build',
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: process.env.GENERATE_SOURCEMAP === 'true',
|
||||||
|
target: process.env.ELECTRON ? 'electron-renderer' : 'modules',
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
vendor: ['react', 'react-dom', 'react-router-dom']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
base: process.env.PUBLIC_URL || '/',
|
||||||
|
optimizeDeps: {
|
||||||
|
esbuildOptions: {
|
||||||
|
target: 'es2020',
|
||||||
|
define: {
|
||||||
|
global: 'globalThis',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
'ethers',
|
||||||
|
'assert',
|
||||||
|
'buffer',
|
||||||
|
'process',
|
||||||
|
'stream-browserify',
|
||||||
|
'isomorphic-fetch'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
esbuild: {
|
||||||
|
target: 'es2020'
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env.VITE_COMMIT_REF': JSON.stringify(process.env.COMMIT_REF),
|
||||||
|
'global': 'globalThis',
|
||||||
|
'__dirname': '""',
|
||||||
|
}
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user