diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..d7faa7714218e08c70603e9285164946845b8369 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,42 @@ +stages: + - build_staging + - restart_staging + - clean_up + +build_staging: + stage: build_staging + script: + - echo "Building current develop branch" + - ./run.sh build stg + environment: + name: staging + only: + - develop + +restart_staging: + stage: restart_staging + script: + - echo "Stopping current condenser image" + - ./run.sh stop stg + - echo "Starting latest condenser image" + - ./run.sh start stg + dependencies: + - build_staging + environment: + name: staging + only: + - develop + +clean_up: + stage: clean_up + script: + - echo "Cleanup filespace / unused images / containers / networks" + - docker image prune -f + - docker container prune -f + - docker network prune -f + dependencies: + - restart_staging + environment: + name: staging + only: + - develop diff --git a/README.md b/README.md index 4004b2fa706805257a1aebadf2313ef2b063a245..4d1c3638a798615475289048957311ad0442e829 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ # Condenser - -Condenser is the react.js web interface to the world's first and used to be best -blockchain-based social media platform, steemit.com. It uses -[Steen compatible blockchain](https://github.com/steemit/steem), powered by DPoS Governance and ChainBase DB to store JSON-based content for a plethora of web +Condenser is the react.js web interface to the +blockchain-based social media platform, Hive.blog. It uses a +[Hive compatible blockchain](https://gitlab.syncad.com/hive/hive), powered by DPoS Governance and ChainBase DB to store JSON-based content for a plethora of web applications. ## Why would I want to use Condenser? -* Learning how to build blockchain-based web applications using Steem compatible blockchain as a content storage mechanism in react.js -* Reviewing the inner workings of the steemit.com social media platform -* Assisting with software development for steemit.com +* Learning how to build blockchain-based web applications using Hive compatible blockchain as a content storage mechanism in react.js +* Reviewing the inner workings of the Hive.blog social media platform +* Assisting with software development for Hive.blog and Hive.io ## Installation @@ -22,19 +21,31 @@ simple as pulling in the github repo and issuing one command to build it, like this: ```bash -git clone https://github.com/steemit/condenser +git clone https://gitlab.syncad.com/hive/condenser cd condenser docker build -t="myname/condenser:mybranch" . docker run -it -p 8080:8080 myname/condenser:mybranch ``` +#### Docker Compose + +If you like to run and build condenser and additionally a reverse-proxy using an Nginx Docker image, with companion Letsencrypt (SSL) support, you can simple launch the Docker-compose files via the included `run.sh`-scripts. + +```bash +git clone https://gitlab.syncad.com/hive/condenser +cd condenser +./run.sh start proxy # to start the nginx reverse proxy (with ssl support) +./run.sh start (prod|dev|stg) # to build and start the condensor image +./run.sh logs (prod|dev|stg) # (optionally) to attach to the condensor image and inspect its logs +``` + ## Building from source without docker (the 'traditional' way): (better if you're planning to do condenser development) #### Clone the repository and make a tmp folder ```bash -git clone https://github.com/steemit/condenser +git clone https://gitlab.syncad.com/hive/condenser cd condenser mkdir tmp ``` @@ -47,8 +58,8 @@ Condenser is known to successfully build using node 12.6, npm 6.13.4, and yarn 1.22.4. We use the yarn package manager instead of the default `npm`. There are -multiple reasons for this, one being that we have `steem-js` built from -source pulling the github repo as part of the build process and yarn +multiple reasons for this, one being that we have `hive-js` built from +source pulling the gitlab repo as part of the build process and yarn supports this. This way the library that handles keys can be loaded by commit hash instead of a version name and cryptographically verified to be exactly what we expect it to be. Yarn can be installed with `npm`, but @@ -80,12 +91,12 @@ It will take quite a bit longer to start in this mode (~60s) as it needs to build and start the webpack-dev-server. By default you will be connected to community public api node at -`https://api.steem.house`. This is actually on the real blockchain and +`https://api.hive.blog`. This is actually on the real blockchain and you would use your regular account name and credentials to login - there is not an official separate testnet at this time. If you intend to run a full-fledged site relying on your own, we recommend looking into running a -copy of `steemd` locally instead -[https://github.com/steemit/steem](https://github.com/steemit/steem). +copy of `hive (steemd)` locally instead +[https://gitlab.syncad.com/hive/hive](https://gitlab.syncad.com/hive/hive). #### Debugging SSR code @@ -104,8 +115,8 @@ stored in `config/defaults.json`. Environment variables using an example like this: ```bash -export SDC_CLIENT_STEEMD_URL="https://api.steem.house" -export SDC_SERVER_STEEMD_URL="https://api.steem.house" +export SDC_CLIENT_STEEMD_URL="https://api.hive.blog" +export SDC_SERVER_STEEMD_URL="https://api.hive.blog" ``` Keep in mind environment variables only exist in your active session, so if @@ -187,16 +198,6 @@ OFFLINE_SSR_TEST=true NODE_ENV=production node --prof lib/server/index.js This will read data from the blobs in `api_mockdata` directory. If you want to use another set of mock data, create a similar directory to that one and add an argument `OFFLINE_SSR_TEST_DATA_DIR` pointing to your new directory. -### Run blackbox tests using nightwatch - -To run a Selenium test suite, start the condenser docker image with a name `condenser` (like `docker run --name condenser -itp 8080:8080 steemit/condenser:latest`) and then run the blackboxtest image attached to the condneser image's network: - -``` -docker build -t=steemit/condenser-blackboxtest blackboxtest/ -docker run --network container:condenser steemit/condenser-blackboxtest:latest - -``` - ## Issues To report a non-critical issue, please file an issue on this GitHub project. diff --git a/config/default.json b/config/default.json index a6c916ebaa968f732798d5e331f6424127868f47..1cfbfdcb2e565fa485f62891d8f1aed7aec063e9 100644 --- a/config/default.json +++ b/config/default.json @@ -4,7 +4,7 @@ "helmet": { "directives": { "childSrc": "'self' 3speak.online emb.d.tube player.twitch.tv www.youtube.com staticxx.facebook.com w.soundcloud.com player.vimeo.com", - "connectSrc": "https://images.hive.blog 'self' hive.blog https://api.hive.blog api.blocktrades.us", + "connectSrc": "https://images.hive.blog 'self' hive.blog https://api.hive.blog api.blocktrades.us https://anyx.io", "defaultSrc": "tpc.googlesyndication.com 'self' img.3speakcontent.online emb.d.tube www.youtube.com staticxx.facebook.com player.vimeo.com *.streamrail.com", "fontSrc": "data: fonts.gstatic.com", "frameAncestors": "'none'", @@ -39,6 +39,8 @@ "steemd_connection_server": "https://api.hive.blog", "steemd_use_appbase": false, "chain_id": "0000000000000000000000000000000000000000000000000000000000000000", + "alternative_api_endpoints": ["https://api.hive.blog", "https://anyx.io"], + "failover_threshold": 3, "address_prefix": "STM", "conveyor_posting_wif": false, "conveyor_username": false, diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index d576d9996323f910fc2aae99b2f4782c6f0d416b..4beefcfd21cf5beac471cb5c986906f768f7aea6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -19,7 +19,7 @@ services: SDC_IMAGE_PROXY_PREFIX: https://images.hive.blog/ SDC_UPLOAD_IMAGE_URL: https://images.hive.blog - SDC_HELMET_CONNECTSRC: "'self' http://hiveblog.local https://api.hive.blog https://images.hive.blog" + SDC_HELMET_CONNECTSRC: "'self' http://hiveblog.local https://api.hive.blog https://images.hive.blog https://anyx.io" volumes: - ./yarn.lock:/var/app/yarn.lock - ./package.json:/var/app/package.json diff --git a/docker-compose.proxy.yml b/docker-compose.proxy.yml index a3e44df14ccb589df39748c3588ce28d9b6bd8d7..bd14cdafc7149214c680e0f80600afcbfc190ffe 100644 --- a/docker-compose.proxy.yml +++ b/docker-compose.proxy.yml @@ -3,16 +3,60 @@ services: proxy: image: jwilder/nginx-proxy:latest container_name: proxy + labels: + com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true" ports: - "80:80" - "443:443" volumes: + - letsencrypt-certs:/etc/nginx/certs:ro + - nginx-vhost:/etc/nginx/vhost.d + - nginx-html:/usr/share/nginx/html + - nginx-conf:/etc/nginx/conf.d - /var/run/docker.sock:/tmp/docker.sock:ro - ./client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro networks: - reverse-proxy restart: always + letsencrypt: + image: jrcs/letsencrypt-nginx-proxy-companion:latest + container_name: letsencrypt + depends_on: + - proxy + environment: + NGINX_PROXY_CONTAINER: proxy + volumes: + - letsencrypt-certs:/etc/nginx/certs + - nginx-vhost:/etc/nginx/vhost.d + - nginx-html:/usr/share/nginx/html + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - reverse-proxy + restart: always + networks: reverse-proxy: driver: bridge + +volumes: + letsencrypt-certs: + driver_opts: + type: none + device: /etc/letsencrypt/certs + o: bind + nginx-conf: + driver_opts: + type: none + device: /etc/nginx/conf.d + o: bind + nginx-vhost: + driver_opts: + type: none + device: /etc/nginx/vhost.d + o: bind + nginx-html: + driver_opts: + type: none + device: /var/www/html + o: bind diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml new file mode 100644 index 0000000000000000000000000000000000000000..9a0f0e082d582d65cec3d7e9a4811c45f8fde430 --- /dev/null +++ b/docker-compose.staging.yml @@ -0,0 +1,27 @@ +version: "3" +services: + hive_condenser_stg: + build: . + image: hive/condenser:staging + container_name: hive_condenser_stg + environment: + VIRTUAL_HOST: staging-blog.hive.io + VIRTUAL_PORT: 8080 + LETSENCRYPT_HOST: staging-blog.hive.io + LETSENCRYPT_EMAIL: certs@hive.io + SDC_CLIENT_STEEMD_URL: https://api.hive.blog + SDC_SERVER_STEEMD_URL: https://api.hive.blog + SDC_DISABLE_SIGNUPS: 1 + SDC_SITE_DOMAIN: staging-blog.hive.io + SDC_IMAGE_PROXY_PREFIX: https://images.hive.blog/ + SDC_UPLOAD_IMAGE_URL: https://images.hive.blog + SDC_HELMET_CONNECTSRC: "'self' https://api.hive.blog https://staging.hive.blog https://images.hive.blog https://anyx.io" + WALLET_URL: https://wallet.hive.blog + networks: + - condenser_reverse-proxy + restart: always + +networks: + condenser_reverse-proxy: + external: + name: condenser_reverse-proxy diff --git a/package.json b/package.json index b2081b44a006bea03c093607e4228729f1538cb5..69e7c43ad27f25fb0944a88c92dbfb2508d08b44 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "license": "MIT", "dependencies": { "@hivechain/hivescript": "^1.1.3", - "@hiveio/hive-js": "0.0.1", + "@hiveio/hive-js": "^0.0.2", "@steem/crypto-session": "git+https://github.com/steemit/crypto-session.git#83a90b319ce5bc6a70362d52a15a815de7e729bb", "assert": "1.4.1", "autoprefixer-loader": "3.2.0", diff --git a/run.sh b/run.sh index 47fe5dc34f2f32fa419e98fcf3711236e9fe59c0..341368ee4b8ecd1666fadc697983531788196a3e 100755 --- a/run.sh +++ b/run.sh @@ -10,6 +10,10 @@ function start { docker-compose -f docker-compose.prod.yml up -d ;; + "stg") + docker-compose -f docker-compose.staging.yml up -d + ;; + "dev") docker-compose -f docker-compose.dev.yml up ;; @@ -31,6 +35,10 @@ function stop { docker-compose -f docker-compose.prod.yml down ;; + "stg") + docker-compose -f docker-compose.staging.yml down + ;; + "dev") docker-compose -f docker-compose.dev.yml down ;; @@ -52,6 +60,10 @@ function logs { docker-compose -f docker-compose.prod.yml logs --tail 30 --follow ;; + "stg") + docker-compose -f docker-compose.staging.yml logs --tail 30 --follow + ;; + "*") echo Unknown environment exit 1 @@ -70,6 +82,10 @@ function build { docker-compose -f docker-compose.prod.yml build ;; + "stg") + docker-compose -f docker-compose.staging.yml build + ;; + "*") echo Unknown environment exit 1 @@ -124,7 +140,7 @@ while test $# -gt 0; do ;; *) - echo "Usage: ./run.sh <start|stop|log> <prod|dev>" + echo "Usage: ./run.sh <start|stop|log> <prod|stg|dev>" exit 1 ;; esac diff --git a/src/app/Main.js b/src/app/Main.js index bacfa23879f540f62c25e2dbe0fd53ec9636da39..5d632dbad9185fd2ff8321236180403c1a7fdbaf 100644 --- a/src/app/Main.js +++ b/src/app/Main.js @@ -85,12 +85,22 @@ function runApp(initial_state) { const config = initial_state.offchain.config; steem.api.setOptions({ - url: config.steemd_connection_client, + url: + localStorage.getItem('user_preferred_api_endpoint') === null + ? config.steemd_connection_client + : localStorage.getItem('user_preferred_api_endpoint'), retry: true, useAppbaseApi: !!config.steemd_use_appbase, + alternative_api_endpoints: config.alternative_api_endpoints, + failover_threshold: config.failover_threshold, }); steem.config.set('address_prefix', config.address_prefix); steem.config.set('chain_id', config.chain_id); + steem.config.set('failover_threshold', config.failover_threshold); + steem.config.set( + 'alternative_api_endpoints', + config.alternative_api_endpoints + ); window.$STM_Config = config; plugins(config); if (initial_state.offchain.serverBusy) { diff --git a/src/app/assets/images/hive-blog-logo-nightmode.svg b/src/app/assets/images/hive-blog-logo-nightmode.svg index fb0675bfceedfaddde5b2fcae8e45c4ab48ef35c..90e099c4908d54377e7af14df6894a8e14904a0d 100644 --- a/src/app/assets/images/hive-blog-logo-nightmode.svg +++ b/src/app/assets/images/hive-blog-logo-nightmode.svg @@ -1,15 +1,10 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Generator: GIMP export as svg plugin --> - -<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="373px" height="109px" viewBox="0 0 373 109" version="1.1"><defs id="defs2" /> -<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:document-units="px" showgrid="false" inkscape:window-maximized="1" /> -<metadata id="metadata5"> -<rdf:RDF> -<cc:Work rdf:about=""> -<dc:format>image/svg+xml</dc:format> -<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> -<dc:title></dc:title> -</cc:Work> -</rdf:RDF> -</metadata><g inkscape:groupmode="layer" inkscape:label="hive-blog-logo.png" ><image xlink:href="data:image/png;base64,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" x="0" y="0" width="373" height="109" /> -</g></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="373" height="109" viewBox="0 0 373 109"> + <g fill="none"> + <g fill="#E31337"> + <path d="M90.4422108,61.5356296 C90.85992,61.5356296 91.1207865,61.9874183 90.9115288,62.3484305 L64.0269732,108.729763 C63.930027,108.897015 63.7511741,109 63.5576553,109 L47.1234647,109 C46.7057556,109 46.4448891,108.548211 46.6541468,108.187199 L73.5387023,61.8058664 C73.6356485,61.6386145 73.8145015,61.5356296 74.0080203,61.5356296 L90.4422108,61.5356296 Z M74.4582937,48.2411837 C74.2647749,48.2411837 74.0859219,48.1381988 73.9889757,47.9709469 L46.6541468,0.812800858 C46.4448891,0.451788656 46.7057556,0 47.1234647,0 L63.5576553,0 C63.7511741,0 63.930027,0.102984848 64.0269732,0.270236757 L91.3618022,47.4283828 C91.5710599,47.789395 91.3101934,48.2411837 90.8924842,48.2411837 L74.4582937,48.2411837 Z"/> + <path d="M77.7078135.812538775C77.4981696.45152632 77.7598718 0 78.1787565 0L94.6363581 0C94.8305929 0 95.0100903.10309919 95.1073011.27049884L126.44159 54.22898C126.538963 54.3966589 126.538963 54.6033411 126.44159 54.77102L95.1073011 108.729501C95.0100903 108.896901 94.8305929 109 94.6363581 109L78.1787565 109C77.7598718 109 77.4981696 108.548474 77.7078135 108.187461L108.884718 54.5 77.7078135.812538775zM64.3328086 54.2269735C64.431852 54.3948735 64.4323504 54.6024365 64.3341146 54.7708006L32.850801 108.729282C32.6407695 109.089249 32.1162201 109.090483 31.9044622 108.731508L.0746342025 54.7730265C-.0244091387 54.6051265-.024907567 54.3975635.0733282916 54.2291994L31.5566419.270718284C31.7666733-.0892494466 32.2912227-.0904832685 32.5029806.268492348L64.3328086 54.2269735z"/> + </g> + <path fill="#FFFFFF" d="M186.740404,0 L199.453228,0 L199.453228,46.87 L186.740404,46.87 L186.740404,28.5907 L168.674812,28.5907 L168.674812,46.87 L155.961988,46.87 L155.961988,0 L168.674812,0 L168.674812,16.9401571 L186.740404,16.9401571 L186.740404,0 Z M226.217067,0 L238.762617,0 L238.762617,46.87 L226.217067,46.87 L226.217067,0 Z M279.750831,46.87 L261.344607,0.602614286 L261.344607,0 L275.132542,0 L286.644799,31.8046429 L298.157056,0 L311.944991,0 L311.944991,0.602614286 L293.538767,46.87 L279.750831,46.87 Z M346.849018,35.3533714 L373,35.3533714 L373,46.87 L334.108796,46.87 L334.108796,0 L372.530623,0 L372.530623,11.5166286 L346.849018,11.5166286 L346.849018,18.0114714 L363.143091,18.0114714 L363.143091,28.7246143 L346.849018,28.7246143 L346.849018,35.3533714 Z"/> + <path fill="#E31337" d="M175.27868,81.8564453 C178.243977,82.9634821 180.478506,84.538857 181.982335,86.5826172 C183.486164,88.6263774 184.238067,91.0533063 184.238067,93.8634766 C184.238067,97.7806836 182.829573,101.027233 180.012541,103.603223 C177.195509,106.179212 173.520715,107.467187 168.988047,107.467187 L155.961988,107.467187 L155.961988,60.7164062 L165.429709,60.7164062 C170.343629,60.7164062 174.081965,61.7276266 176.644828,63.7500977 C179.207692,65.7725687 180.489104,68.6572078 180.489104,72.4041016 C180.489104,76.491622 178.752314,79.6423717 175.27868,81.8564453 Z M161.311517,79.8765625 L163.507299,79.8765625 C167.382228,79.8765625 170.277618,79.2804747 172.193556,78.0882812 C174.109493,76.8960878 175.067447,75.0013802 175.067447,72.4041016 C175.067447,68.1888461 172.11824,66.08125 166.219736,66.08125 L161.311517,66.08125 L161.311517,79.8765625 Z M161.311517,102.102344 L166.630025,102.102344 C169.959515,102.102344 172.380925,101.780573 173.89433,101.137023 C175.407734,100.493472 176.618439,99.5067099 177.526482,98.1767056 C178.434524,96.8467014 178.888539,95.4952657 178.888539,94.1223581 C178.888539,92.7494504 178.618292,91.5374486 178.077791,90.4863162 C177.537289,89.4351838 176.737359,88.5020496 175.677976,87.6868857 C174.618593,86.8717218 173.321409,86.2603581 171.786384,85.8527761 C170.25136,85.4451942 167.473224,85.2414062 163.451892,85.2414062 L161.311517,85.2414062 L161.311517,102.102344 Z M231.619605,107.467187 L207.928836,107.467187 L207.928836,60.7164062 L213.606789,60.7164062 L213.606789,102.166211 L231.619605,102.166211 L231.619605,107.467187 Z M276.423021,59.95 C280.948221,59.95 285.113879,61.0250869 288.920122,63.175293 C292.726365,65.325499 295.71845,68.2952936 297.896466,72.0847656 C300.074483,75.8742377 301.163475,79.9936301 301.163475,84.4430664 C301.163475,88.8925027 300.085056,93.0118951 297.928185,96.8013672 C295.771314,100.590839 292.810947,103.571278 289.046996,105.742773 C285.283045,107.914269 281.191396,109 276.771925,109 C272.352454,109 268.239659,107.924913 264.433416,105.774707 C260.627174,103.624501 257.645662,100.665351 255.488791,96.897168 C253.33192,93.1289851 252.253501,89.0202371 252.253501,84.5708008 C252.253501,80.1213645 253.33192,75.9913276 255.488791,72.1805664 C257.645662,68.3698052 260.595455,65.3787218 264.338261,63.2072266 C268.081066,61.0357313 272.109279,59.95 276.423021,59.95 Z M276.611833,103.635156 C281.981578,103.635156 286.524315,101.825604 290.240179,98.2064453 C293.956042,94.5872866 295.813947,90.0527616 295.813947,84.6027344 C295.813947,79.1527071 293.934564,74.5756045 290.175742,70.8712891 C286.41692,67.1669737 281.895662,65.3148437 276.611833,65.3148437 C271.199129,65.3148437 266.677872,67.1776181 263.047924,70.9032227 C259.417976,74.6288272 257.603029,79.1633522 257.603029,84.5069336 C257.603029,89.850515 259.471672,94.3743955 263.209015,98.0787109 C266.946358,101.783026 271.413919,103.635156 276.611833,103.635156 Z M373,84.9859375 C372.87263,92.5648816 370.675524,98.461893 366.408617,102.677148 C362.14171,106.892404 356.526884,109 349.563971,109 C341.582095,109 335.213672,106.541138 330.458512,101.62334 C325.703352,96.7055418 323.325807,90.946908 323.325807,84.3472656 C323.325807,77.7476233 325.703352,72.0315671 330.458512,67.1989258 C335.213672,62.3662844 341.348586,59.95 348.863437,59.95 C352.939289,59.95 356.7285,60.7057542 360.231185,62.2172852 C363.73387,63.7288162 367.162204,66.0386563 370.51629,69.146875 L366.313089,73.1705078 C363.723225,70.6583859 360.921119,68.731745 357.906688,67.3905273 C354.892256,66.0493097 351.792957,65.3787109 348.608698,65.3787109 C345.424439,65.3787109 342.261455,66.2302649 339.119653,67.9333984 C335.977851,69.636532 333.515394,71.9357277 331.732209,74.8310547 C329.949024,77.7263817 329.057445,80.8558426 329.057445,84.2195313 C329.057445,89.4566668 331.021042,94.0231251 334.948295,97.919043 C338.875547,101.814961 343.789847,103.762891 349.69134,103.762891 C354.064389,103.762891 357.842987,102.560071 361.027246,100.154395 C364.211505,97.7487184 366.164488,94.4595912 366.886253,90.2869141 L352.366105,90.2869141 L352.366105,84.9859375 L373,84.9859375 Z"/> + </g> +</svg> diff --git a/src/app/assets/images/hive-blog-logo.svg b/src/app/assets/images/hive-blog-logo.svg index fb0675bfceedfaddde5b2fcae8e45c4ab48ef35c..d4e160862519e20f412993fe8f2c8e7607a2fbb2 100644 --- a/src/app/assets/images/hive-blog-logo.svg +++ b/src/app/assets/images/hive-blog-logo.svg @@ -1,15 +1,10 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Generator: GIMP export as svg plugin --> - -<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="373px" height="109px" viewBox="0 0 373 109" version="1.1"><defs id="defs2" /> -<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:document-units="px" showgrid="false" inkscape:window-maximized="1" /> -<metadata id="metadata5"> -<rdf:RDF> -<cc:Work rdf:about=""> -<dc:format>image/svg+xml</dc:format> -<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> -<dc:title></dc:title> -</cc:Work> -</rdf:RDF> -</metadata><g inkscape:groupmode="layer" inkscape:label="hive-blog-logo.png" ><image xlink:href="data:image/png;base64,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" x="0" y="0" width="373" height="109" /> -</g></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="373" height="109" viewBox="0 0 373 109"> + <g fill="none"> + <g fill="#E31337"> + <path d="M90.4422108,61.5356296 C90.85992,61.5356296 91.1207865,61.9874183 90.9115288,62.3484305 L64.0269732,108.729763 C63.930027,108.897015 63.7511741,109 63.5576553,109 L47.1234647,109 C46.7057556,109 46.4448891,108.548211 46.6541468,108.187199 L73.5387023,61.8058664 C73.6356485,61.6386145 73.8145015,61.5356296 74.0080203,61.5356296 L90.4422108,61.5356296 Z M74.4582937,48.2411837 C74.2647749,48.2411837 74.0859219,48.1381988 73.9889757,47.9709469 L46.6541468,0.812800858 C46.4448891,0.451788656 46.7057556,0 47.1234647,0 L63.5576553,0 C63.7511741,0 63.930027,0.102984848 64.0269732,0.270236757 L91.3618022,47.4283828 C91.5710599,47.789395 91.3101934,48.2411837 90.8924842,48.2411837 L74.4582937,48.2411837 Z"/> + <path d="M77.7078135.812538775C77.4981696.45152632 77.7598718 0 78.1787565 0L94.6363581 0C94.8305929 0 95.0100903.10309919 95.1073011.27049884L126.44159 54.22898C126.538963 54.3966589 126.538963 54.6033411 126.44159 54.77102L95.1073011 108.729501C95.0100903 108.896901 94.8305929 109 94.6363581 109L78.1787565 109C77.7598718 109 77.4981696 108.548474 77.7078135 108.187461L108.884718 54.5 77.7078135.812538775zM64.3328086 54.2269735C64.431852 54.3948735 64.4323504 54.6024365 64.3341146 54.7708006L32.850801 108.729282C32.6407695 109.089249 32.1162201 109.090483 31.9044622 108.731508L.0746342025 54.7730265C-.0244091387 54.6051265-.024907567 54.3975635.0733282916 54.2291994L31.5566419.270718284C31.7666733-.0892494466 32.2912227-.0904832685 32.5029806.268492348L64.3328086 54.2269735z"/> + </g> + <path fill="#212529" d="M186.740404,0 L199.453228,0 L199.453228,46.87 L186.740404,46.87 L186.740404,28.5907 L168.674812,28.5907 L168.674812,46.87 L155.961988,46.87 L155.961988,0 L168.674812,0 L168.674812,16.9401571 L186.740404,16.9401571 L186.740404,0 Z M226.217067,0 L238.762617,0 L238.762617,46.87 L226.217067,46.87 L226.217067,0 Z M279.750831,46.87 L261.344607,0.602614286 L261.344607,0 L275.132542,0 L286.644799,31.8046429 L298.157056,0 L311.944991,0 L311.944991,0.602614286 L293.538767,46.87 L279.750831,46.87 Z M346.849018,35.3533714 L373,35.3533714 L373,46.87 L334.108796,46.87 L334.108796,0 L372.530623,0 L372.530623,11.5166286 L346.849018,11.5166286 L346.849018,18.0114714 L363.143091,18.0114714 L363.143091,28.7246143 L346.849018,28.7246143 L346.849018,35.3533714 Z"/> + <path fill="#E31337" d="M175.27868,81.8564453 C178.243977,82.9634821 180.478506,84.538857 181.982335,86.5826172 C183.486164,88.6263774 184.238067,91.0533063 184.238067,93.8634766 C184.238067,97.7806836 182.829573,101.027233 180.012541,103.603223 C177.195509,106.179212 173.520715,107.467187 168.988047,107.467187 L155.961988,107.467187 L155.961988,60.7164062 L165.429709,60.7164062 C170.343629,60.7164062 174.081965,61.7276266 176.644828,63.7500977 C179.207692,65.7725687 180.489104,68.6572078 180.489104,72.4041016 C180.489104,76.491622 178.752314,79.6423717 175.27868,81.8564453 Z M161.311517,79.8765625 L163.507299,79.8765625 C167.382228,79.8765625 170.277618,79.2804747 172.193556,78.0882812 C174.109493,76.8960878 175.067447,75.0013802 175.067447,72.4041016 C175.067447,68.1888461 172.11824,66.08125 166.219736,66.08125 L161.311517,66.08125 L161.311517,79.8765625 Z M161.311517,102.102344 L166.630025,102.102344 C169.959515,102.102344 172.380925,101.780573 173.89433,101.137023 C175.407734,100.493472 176.618439,99.5067099 177.526482,98.1767056 C178.434524,96.8467014 178.888539,95.4952657 178.888539,94.1223581 C178.888539,92.7494504 178.618292,91.5374486 178.077791,90.4863162 C177.537289,89.4351838 176.737359,88.5020496 175.677976,87.6868857 C174.618593,86.8717218 173.321409,86.2603581 171.786384,85.8527761 C170.25136,85.4451942 167.473224,85.2414062 163.451892,85.2414062 L161.311517,85.2414062 L161.311517,102.102344 Z M231.619605,107.467187 L207.928836,107.467187 L207.928836,60.7164062 L213.606789,60.7164062 L213.606789,102.166211 L231.619605,102.166211 L231.619605,107.467187 Z M276.423021,59.95 C280.948221,59.95 285.113879,61.0250869 288.920122,63.175293 C292.726365,65.325499 295.71845,68.2952936 297.896466,72.0847656 C300.074483,75.8742377 301.163475,79.9936301 301.163475,84.4430664 C301.163475,88.8925027 300.085056,93.0118951 297.928185,96.8013672 C295.771314,100.590839 292.810947,103.571278 289.046996,105.742773 C285.283045,107.914269 281.191396,109 276.771925,109 C272.352454,109 268.239659,107.924913 264.433416,105.774707 C260.627174,103.624501 257.645662,100.665351 255.488791,96.897168 C253.33192,93.1289851 252.253501,89.0202371 252.253501,84.5708008 C252.253501,80.1213645 253.33192,75.9913276 255.488791,72.1805664 C257.645662,68.3698052 260.595455,65.3787218 264.338261,63.2072266 C268.081066,61.0357313 272.109279,59.95 276.423021,59.95 Z M276.611833,103.635156 C281.981578,103.635156 286.524315,101.825604 290.240179,98.2064453 C293.956042,94.5872866 295.813947,90.0527616 295.813947,84.6027344 C295.813947,79.1527071 293.934564,74.5756045 290.175742,70.8712891 C286.41692,67.1669737 281.895662,65.3148437 276.611833,65.3148437 C271.199129,65.3148437 266.677872,67.1776181 263.047924,70.9032227 C259.417976,74.6288272 257.603029,79.1633522 257.603029,84.5069336 C257.603029,89.850515 259.471672,94.3743955 263.209015,98.0787109 C266.946358,101.783026 271.413919,103.635156 276.611833,103.635156 Z M373,84.9859375 C372.87263,92.5648816 370.675524,98.461893 366.408617,102.677148 C362.14171,106.892404 356.526884,109 349.563971,109 C341.582095,109 335.213672,106.541138 330.458512,101.62334 C325.703352,96.7055418 323.325807,90.946908 323.325807,84.3472656 C323.325807,77.7476233 325.703352,72.0315671 330.458512,67.1989258 C335.213672,62.3662844 341.348586,59.95 348.863437,59.95 C352.939289,59.95 356.7285,60.7057542 360.231185,62.2172852 C363.73387,63.7288162 367.162204,66.0386563 370.51629,69.146875 L366.313089,73.1705078 C363.723225,70.6583859 360.921119,68.731745 357.906688,67.3905273 C354.892256,66.0493097 351.792957,65.3787109 348.608698,65.3787109 C345.424439,65.3787109 342.261455,66.2302649 339.119653,67.9333984 C335.977851,69.636532 333.515394,71.9357277 331.732209,74.8310547 C329.949024,77.7263817 329.057445,80.8558426 329.057445,84.2195313 C329.057445,89.4566668 331.021042,94.0231251 334.948295,97.919043 C338.875547,101.814961 343.789847,103.762891 349.69134,103.762891 C354.064389,103.762891 357.842987,102.560071 361.027246,100.154395 C364.211505,97.7487184 366.164488,94.4595912 366.886253,90.2869141 L352.366105,90.2869141 L352.366105,84.9859375 L373,84.9859375 Z"/> + </g> +</svg> diff --git a/src/app/assets/images/steemit.svg b/src/app/assets/images/steemit.svg index fb0675bfceedfaddde5b2fcae8e45c4ab48ef35c..d4e160862519e20f412993fe8f2c8e7607a2fbb2 100644 --- a/src/app/assets/images/steemit.svg +++ b/src/app/assets/images/steemit.svg @@ -1,15 +1,10 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Generator: GIMP export as svg plugin --> - -<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="373px" height="109px" viewBox="0 0 373 109" version="1.1"><defs id="defs2" /> -<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:document-units="px" showgrid="false" inkscape:window-maximized="1" /> -<metadata id="metadata5"> -<rdf:RDF> -<cc:Work rdf:about=""> -<dc:format>image/svg+xml</dc:format> -<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> -<dc:title></dc:title> -</cc:Work> -</rdf:RDF> -</metadata><g inkscape:groupmode="layer" inkscape:label="hive-blog-logo.png" ><image xlink:href="data:image/png;base64,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" x="0" y="0" width="373" height="109" /> -</g></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="373" height="109" viewBox="0 0 373 109"> + <g fill="none"> + <g fill="#E31337"> + <path d="M90.4422108,61.5356296 C90.85992,61.5356296 91.1207865,61.9874183 90.9115288,62.3484305 L64.0269732,108.729763 C63.930027,108.897015 63.7511741,109 63.5576553,109 L47.1234647,109 C46.7057556,109 46.4448891,108.548211 46.6541468,108.187199 L73.5387023,61.8058664 C73.6356485,61.6386145 73.8145015,61.5356296 74.0080203,61.5356296 L90.4422108,61.5356296 Z M74.4582937,48.2411837 C74.2647749,48.2411837 74.0859219,48.1381988 73.9889757,47.9709469 L46.6541468,0.812800858 C46.4448891,0.451788656 46.7057556,0 47.1234647,0 L63.5576553,0 C63.7511741,0 63.930027,0.102984848 64.0269732,0.270236757 L91.3618022,47.4283828 C91.5710599,47.789395 91.3101934,48.2411837 90.8924842,48.2411837 L74.4582937,48.2411837 Z"/> + <path d="M77.7078135.812538775C77.4981696.45152632 77.7598718 0 78.1787565 0L94.6363581 0C94.8305929 0 95.0100903.10309919 95.1073011.27049884L126.44159 54.22898C126.538963 54.3966589 126.538963 54.6033411 126.44159 54.77102L95.1073011 108.729501C95.0100903 108.896901 94.8305929 109 94.6363581 109L78.1787565 109C77.7598718 109 77.4981696 108.548474 77.7078135 108.187461L108.884718 54.5 77.7078135.812538775zM64.3328086 54.2269735C64.431852 54.3948735 64.4323504 54.6024365 64.3341146 54.7708006L32.850801 108.729282C32.6407695 109.089249 32.1162201 109.090483 31.9044622 108.731508L.0746342025 54.7730265C-.0244091387 54.6051265-.024907567 54.3975635.0733282916 54.2291994L31.5566419.270718284C31.7666733-.0892494466 32.2912227-.0904832685 32.5029806.268492348L64.3328086 54.2269735z"/> + </g> + <path fill="#212529" d="M186.740404,0 L199.453228,0 L199.453228,46.87 L186.740404,46.87 L186.740404,28.5907 L168.674812,28.5907 L168.674812,46.87 L155.961988,46.87 L155.961988,0 L168.674812,0 L168.674812,16.9401571 L186.740404,16.9401571 L186.740404,0 Z M226.217067,0 L238.762617,0 L238.762617,46.87 L226.217067,46.87 L226.217067,0 Z M279.750831,46.87 L261.344607,0.602614286 L261.344607,0 L275.132542,0 L286.644799,31.8046429 L298.157056,0 L311.944991,0 L311.944991,0.602614286 L293.538767,46.87 L279.750831,46.87 Z M346.849018,35.3533714 L373,35.3533714 L373,46.87 L334.108796,46.87 L334.108796,0 L372.530623,0 L372.530623,11.5166286 L346.849018,11.5166286 L346.849018,18.0114714 L363.143091,18.0114714 L363.143091,28.7246143 L346.849018,28.7246143 L346.849018,35.3533714 Z"/> + <path fill="#E31337" d="M175.27868,81.8564453 C178.243977,82.9634821 180.478506,84.538857 181.982335,86.5826172 C183.486164,88.6263774 184.238067,91.0533063 184.238067,93.8634766 C184.238067,97.7806836 182.829573,101.027233 180.012541,103.603223 C177.195509,106.179212 173.520715,107.467187 168.988047,107.467187 L155.961988,107.467187 L155.961988,60.7164062 L165.429709,60.7164062 C170.343629,60.7164062 174.081965,61.7276266 176.644828,63.7500977 C179.207692,65.7725687 180.489104,68.6572078 180.489104,72.4041016 C180.489104,76.491622 178.752314,79.6423717 175.27868,81.8564453 Z M161.311517,79.8765625 L163.507299,79.8765625 C167.382228,79.8765625 170.277618,79.2804747 172.193556,78.0882812 C174.109493,76.8960878 175.067447,75.0013802 175.067447,72.4041016 C175.067447,68.1888461 172.11824,66.08125 166.219736,66.08125 L161.311517,66.08125 L161.311517,79.8765625 Z M161.311517,102.102344 L166.630025,102.102344 C169.959515,102.102344 172.380925,101.780573 173.89433,101.137023 C175.407734,100.493472 176.618439,99.5067099 177.526482,98.1767056 C178.434524,96.8467014 178.888539,95.4952657 178.888539,94.1223581 C178.888539,92.7494504 178.618292,91.5374486 178.077791,90.4863162 C177.537289,89.4351838 176.737359,88.5020496 175.677976,87.6868857 C174.618593,86.8717218 173.321409,86.2603581 171.786384,85.8527761 C170.25136,85.4451942 167.473224,85.2414062 163.451892,85.2414062 L161.311517,85.2414062 L161.311517,102.102344 Z M231.619605,107.467187 L207.928836,107.467187 L207.928836,60.7164062 L213.606789,60.7164062 L213.606789,102.166211 L231.619605,102.166211 L231.619605,107.467187 Z M276.423021,59.95 C280.948221,59.95 285.113879,61.0250869 288.920122,63.175293 C292.726365,65.325499 295.71845,68.2952936 297.896466,72.0847656 C300.074483,75.8742377 301.163475,79.9936301 301.163475,84.4430664 C301.163475,88.8925027 300.085056,93.0118951 297.928185,96.8013672 C295.771314,100.590839 292.810947,103.571278 289.046996,105.742773 C285.283045,107.914269 281.191396,109 276.771925,109 C272.352454,109 268.239659,107.924913 264.433416,105.774707 C260.627174,103.624501 257.645662,100.665351 255.488791,96.897168 C253.33192,93.1289851 252.253501,89.0202371 252.253501,84.5708008 C252.253501,80.1213645 253.33192,75.9913276 255.488791,72.1805664 C257.645662,68.3698052 260.595455,65.3787218 264.338261,63.2072266 C268.081066,61.0357313 272.109279,59.95 276.423021,59.95 Z M276.611833,103.635156 C281.981578,103.635156 286.524315,101.825604 290.240179,98.2064453 C293.956042,94.5872866 295.813947,90.0527616 295.813947,84.6027344 C295.813947,79.1527071 293.934564,74.5756045 290.175742,70.8712891 C286.41692,67.1669737 281.895662,65.3148437 276.611833,65.3148437 C271.199129,65.3148437 266.677872,67.1776181 263.047924,70.9032227 C259.417976,74.6288272 257.603029,79.1633522 257.603029,84.5069336 C257.603029,89.850515 259.471672,94.3743955 263.209015,98.0787109 C266.946358,101.783026 271.413919,103.635156 276.611833,103.635156 Z M373,84.9859375 C372.87263,92.5648816 370.675524,98.461893 366.408617,102.677148 C362.14171,106.892404 356.526884,109 349.563971,109 C341.582095,109 335.213672,106.541138 330.458512,101.62334 C325.703352,96.7055418 323.325807,90.946908 323.325807,84.3472656 C323.325807,77.7476233 325.703352,72.0315671 330.458512,67.1989258 C335.213672,62.3662844 341.348586,59.95 348.863437,59.95 C352.939289,59.95 356.7285,60.7057542 360.231185,62.2172852 C363.73387,63.7288162 367.162204,66.0386563 370.51629,69.146875 L366.313089,73.1705078 C363.723225,70.6583859 360.921119,68.731745 357.906688,67.3905273 C354.892256,66.0493097 351.792957,65.3787109 348.608698,65.3787109 C345.424439,65.3787109 342.261455,66.2302649 339.119653,67.9333984 C335.977851,69.636532 333.515394,71.9357277 331.732209,74.8310547 C329.949024,77.7263817 329.057445,80.8558426 329.057445,84.2195313 C329.057445,89.4566668 331.021042,94.0231251 334.948295,97.919043 C338.875547,101.814961 343.789847,103.762891 349.69134,103.762891 C354.064389,103.762891 357.842987,102.560071 361.027246,100.154395 C364.211505,97.7487184 366.164488,94.4595912 366.886253,90.2869141 L352.366105,90.2869141 L352.366105,84.9859375 L373,84.9859375 Z"/> + </g> +</svg> diff --git a/src/app/assets/stylesheets/_themes.scss b/src/app/assets/stylesheets/_themes.scss index f85fa82eb2b96926941772ddf7d70df61829a5d5..6b0e79e6d73fd8da9e9283a09914ffb06ac0dbea 100755 --- a/src/app/assets/stylesheets/_themes.scss +++ b/src/app/assets/stylesheets/_themes.scss @@ -44,7 +44,7 @@ $themes: ( colorAccentHover: $color-hive-red-dark, colorAccentReverse: $color-hive-black, colorWhite: $color-white, - backgroundColor: $color-background-off-white, + backgroundColor: $color-background-off-white-dark, backgroundColorEmphasis: $color-background-almost-white, backgroundColorOpaque: $color-background-off-white, backgroundTransparent: transparent, @@ -53,6 +53,7 @@ $themes: ( moduleMediumBackgroundColor: $color-transparent, navBackgroundColor: $color-white, highlightBackgroundColor: #f3faf0, + tableRowOddBackgroundColor: #e5e5e5, tableRowEvenBackgroundColor: #f4f4f4, border: 1px solid $color-border-light, borderLight: 1px solid $color-border-light-lightest, @@ -94,6 +95,7 @@ $themes: ( moduleMediumBackgroundColor: $color-background-dark, navBackgroundColor: $color-background-less-dark, highlightBackgroundColor: $color-hive-black-darkest, + tableRowOddBackgroundColor: #283239, tableRowEvenBackgroundColor: #212C33, border: 1px solid $color-border-dark-lightest, borderLight: 1px solid $color-border-dark-lightest, diff --git a/src/app/assets/stylesheets/_variables.scss b/src/app/assets/stylesheets/_variables.scss index 5fbd540876874174442c5b8e774d419e0f383e1c..4b97d5c3242a651063055023ab5bf5fcb2933abe 100755 --- a/src/app/assets/stylesheets/_variables.scss +++ b/src/app/assets/stylesheets/_variables.scss @@ -19,7 +19,8 @@ $color-transparent: transparent; $color-background-almost-white:#fafaff; $color-background-off-white: #f8f8ff; -$color-background-off-white-light: #f4f4fd; +$color-background-off-white-dark: #f4f4fd; +$color-background-off-white-darker: #f0f0f09; $color-background-dark: $color-hive-black; $color-background-less-dark: #2c3136; $color-background-super-dark: #191c1f; diff --git a/src/app/components/cards/MarkdownViewer.jsx b/src/app/components/cards/MarkdownViewer.jsx index dda19136d355014484fff83bd5c3280c738f1b67..beae2075b37e22a1b42ae6de0f8cbaadb9884e58 100644 --- a/src/app/components/cards/MarkdownViewer.jsx +++ b/src/app/components/cards/MarkdownViewer.jsx @@ -3,11 +3,11 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Component } from 'react'; import Remarkable from 'remarkable'; -import YoutubePreview from 'app/components/elements/YoutubePreview'; import sanitizeConfig, { noImageText } from 'app/utils/SanitizeConfig'; import sanitize from 'sanitize-html'; import HtmlReady from 'shared/HtmlReady'; import tt from 'counterpart'; +import { generateMd as EmbeddedPlayerGenerateMd } from 'app/components/elements/EmbeddedPlayers'; const remarkable = new Remarkable({ html: true, // remarkable renders first then sanitize runs... @@ -136,114 +136,37 @@ class MarkdownViewer extends Component { let idx = 0; const sections = []; + function checksum(s) { + let chk = 0x12345678; + const len = s.length; + for (let i = 0; i < len; i += 1) { + chk += s.charCodeAt(i) * (i + 1); + } + + return (chk & 0xffffffff).toString(16); + } + // HtmlReady inserts ~~~ embed:${id} type ~~~ for (let section of cleanText.split('~~~ embed:')) { - const match = section.match( - /^([A-Za-z0-9\?\=\_\-\/\.]+) (youtube|vimeo|twitch|dtube|threespeak)\s?(\d+)? ~~~/ - ); - if (match && match.length >= 3) { - const id = match[1]; - const type = match[2]; - const startTime = match[3] ? parseInt(match[3]) : 0; - const w = large ? 640 : 480, - h = large ? 360 : 270; - - if (type === 'youtube') { - sections.push( - <YoutubePreview - key={id} - width={w} - height={h} - youTubeId={id} - startTime={startTime} - frameBorder="0" - allowFullScreen="true" - /> - ); - } else if (type === 'threespeak') { - const url = `https://3speak.online/embed?v=${id}`; - sections.push( - <div className="videoWrapper" key={id}> - <iframe - src={url} - width={w} - height={h} - frameBorder="0" - webkitallowfullscreen="true" - mozallowfullscreen="true" - allowFullScreen - title={`ThreeSpeak video ${id}`} - /> - </div> - ); - } else if (type === 'vimeo') { - const url = `https://player.vimeo.com/video/${id}#t=${ - startTime - }s`; - sections.push( - <div className="videoWrapper" key={id}> - <iframe - src={url} - width={w} - height={h} - frameBorder="0" - webkitallowfullscreen="true" - mozallowfullscreen="true" - allowFullScreen - title={`Vimeo video ${id}`} - /> - </div> - ); - } else if (type === 'twitch') { - const url = `https://player.twitch.tv/${id}`; - sections.push( - <div className="videoWrapper" key={id}> - <iframe - src={url} - width={w} - height={h} - frameBorder="0" - webkitallowfullscreen="true" - mozallowfullscreen="true" - allowFullScreen - title={`Twitch video ${id}`} - /> - </div> - ); - } else if (type === 'dtube') { - const url = `https://emb.d.tube/#!/${id}`; - sections.push( - <div className="videoWrapper" key={id}> - <iframe - src={url} - width={w} - height={h} - frameBorder="0" - webkitallowfullscreen="true" - mozallowfullscreen="true" - allowFullScreen - title={`DTube video ${id}`} - /> - </div> - ); - } else { - console.error('MarkdownViewer unknown embed type', type); - } - if (match[3]) { - section = section.substring( - `${id} ${type} ${startTime} ~~~`.length - ); - } else { - section = section.substring(`${id} ${type} ~~~`.length); + const embedMd = EmbeddedPlayerGenerateMd(section, idx, large); + if (embedMd) { + const { section: newSection, markdown } = embedMd; + section = newSection; + sections.push(markdown); + + if (section === '') { + continue; } - if (section === '') continue; } + sections.push( <div - key={idx++} + key={checksum(section)} dangerouslySetInnerHTML={{ __html: section }} /> ); + + idx += 1; } const cn = @@ -251,12 +174,14 @@ class MarkdownViewer extends Component { (this.props.className ? ` ${this.props.className}` : '') + (html ? ' html' : '') + (large ? '' : ' MarkdownViewer--small'); + return ( <div className={'MarkdownViewer ' + cn}> {sections} {noImageActive && allowNoImage && ( <div + key={'hidden-content'} onClick={this.onAllowNoImage} className="MarkdownViewer__negative_group" > diff --git a/src/app/components/cards/NotificationsList.jsx b/src/app/components/cards/NotificationsList.jsx index 2ac07d91b9f9a7802bd500cbb471db84ef719d14..7228dff74d560c0ac2077d3b93aee3f65de653e5 100644 --- a/src/app/components/cards/NotificationsList.jsx +++ b/src/app/components/cards/NotificationsList.jsx @@ -133,11 +133,19 @@ class NotificationsList extends React.Component { 'notification__item' ); + let visibleCount = 0; for (let ni = 0; ni < notificationElements.length; ni += 1) { const notificationElement = notificationElements[ni]; if (notificationFilter === 'all') { notificationElement.classList.remove('hide'); + + visibleCount += 1; + if (visibleCount % 2 === 0) { + notificationElement.classList.add('even'); + } else { + notificationElement.classList.remove('even'); + } } else if ( this.notificationFilterToTypes.hasOwnProperty( notificationFilter @@ -163,6 +171,13 @@ class NotificationsList extends React.Component { notificationElement.classList.add('hide'); } else { notificationElement.classList.remove('hide'); + visibleCount += 1; + } + + if (visibleCount % 2 === 0) { + notificationElement.classList.add('even'); + } else { + notificationElement.classList.remove('even'); } } } @@ -218,10 +233,13 @@ class NotificationsList extends React.Component { className={`notification__item flex-body notification__${ item.type }`} - style={{ - background: 'rgba(225,255,225,' + item.score + '%)', - }} > + <div className="notification__score"> + <div + className="notification__score_bar" + style={{ width: `${item.score}%` }} + /> + </div> <div className="flex-row"> {mentions && participants && participants[0]} </div> diff --git a/src/app/components/cards/NotificationsList.scss b/src/app/components/cards/NotificationsList.scss index 024ea326006c68f2c1de19c8335969f76e20a714..89e516f3769f2ec2502cbd74c3e1a8bbffb7e25a 100644 --- a/src/app/components/cards/NotificationsList.scss +++ b/src/app/components/cards/NotificationsList.scss @@ -29,17 +29,24 @@ font-weight: bold; } +.notification__item.even { + @include themify($themes) { + background-color: themed('tableRowEvenBackgroundColor'); + } +} + .notification__item { + @include themify($themes) { + background-color: themed('tableRowOddBackgroundColor'); + } align-items: center; padding: 0.5em 1rem; + margin-bottom: 2px; position: relative; - @include themify($themes) { - border-bottom: themed('border'); - } .notification__unread { position: absolute; - right: 1em; + left: 0.4em; top: 0.75em; font-size: 2em; @include themify($themes) { @@ -76,5 +83,32 @@ padding-bottom: 0.5em; padding-right: 0.5em; } + + .notification__message a { + @include themify($themes) { + color: themed('textColorPrimary'); + } + } + + .notification__message a:visited { + @include themify($themes) { + color: themed('textColorSecondary'); + } + } + + .notification__score { + position: absolute; + top: 5px; + right: 5px; + height: 4px; + width: 35px; + background-color: lightgray; + } + + .notification__score_bar { + height: 100%; + position: relative; + background-color: $color-text-hive-red; + } } diff --git a/src/app/components/cards/PostTemplateSelector.jsx b/src/app/components/cards/PostTemplateSelector.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3e49cbfdb158e24b3b4d081d414ebd4ba15177c0 --- /dev/null +++ b/src/app/components/cards/PostTemplateSelector.jsx @@ -0,0 +1,96 @@ +import React from 'react'; +import tt from 'counterpart'; + +class PostTemplateSelector extends React.Component { + static propTypes = { + username: React.PropTypes.string.isRequired, + templates: React.PropTypes.array.isRequired, + onChange: React.PropTypes.func.isRequired, + }; + + constructor() { + super(); + this.state = { + currentTemplateName: '', + }; + } + + render() { + const { username, onChange, templates } = this.props; + const { currentTemplateName } = this.state; + if (!username || typeof window === 'undefined') { + return null; + } + + const handleTemplateSelection = (event, create = false) => { + const selectedTemplateName = event.target.value; + this.setState({ currentTemplateName: selectedTemplateName }); + onChange( + create ? `create_${selectedTemplateName}` : selectedTemplateName + ); + }; + + return ( + <div> + <div className="row"> + <div className="column"> + <h4>{tt('post_template_selector_jsx.templates')}</h4> + <p> + {tt( + 'post_template_selector_jsx.templates_description' + )} + </p> + </div> + </div> + <div className="row"> + <div className="small-12 medium-6 large-12 columns"> + {templates && ( + <select + onChange={handleTemplateSelection} + value={currentTemplateName} + > + <option value=""> + {tt( + 'post_template_selector_jsx.choose_template' + )} + </option> + {templates.map(template => ( + <option + value={template.name} + key={template.name} + > + {template.name} + </option> + ))} + </select> + )} + {!templates && ( + <span> + {tt( + 'post_template_selector_jsx.create_template_first' + )} + </span> + )} + </div> + </div> + <div className="row"> + <div className="small-12 medium-6 large-12 columns"> + <input + id="new_template_name" + type="text" + className="input-group-field bold" + placeholder={tt( + 'post_template_selector_jsx.new_template_name' + )} + onChange={event => { + handleTemplateSelection(event, true); + }} + /> + </div> + </div> + </div> + ); + } +} + +export default PostTemplateSelector; diff --git a/src/app/components/elements/EmbeddedPlayers/dtube.jsx b/src/app/components/elements/EmbeddedPlayers/dtube.jsx new file mode 100644 index 0000000000000000000000000000000000000000..61d5d7d2b35ffeee012d9df3067ed9dbccf8a1a2 --- /dev/null +++ b/src/app/components/elements/EmbeddedPlayers/dtube.jsx @@ -0,0 +1,109 @@ +import React from 'react'; + +/** + * Regular expressions for detecting and validating provider URLs + * @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}} + */ +const regex = { + sanitize: /^https:\/\/emb.d.tube\/\#\!\/([a-zA-Z0-9\-\.\/]+)$/, + main: /https:\/\/(?:emb\.)?(?:d.tube\/\#\!\/(?:v\/)?)([a-zA-Z0-9\-\.\/]*)/, + contentId: /(?:d\.tube\/#!\/(?:v\/)?([a-zA-Z0-9\-\.\/]*))+/, +}; + +export default regex; + +/** + * Generates the Markdown/HTML code to override the detected URL with an iFrame + * @param idx + * @param threespeakId + * @param w + * @param h + * @returns {*} + */ +export function genIframeMd(idx, dtubeId, w, h) { + const url = `https://emb.d.tube/#!/${dtubeId}`; + return ( + <div key={`dtube-${dtubeId}-${idx}`} className="videoWrapper"> + <iframe + title="DTube embedded player" + key={idx} + src={url} + width={w} + height={h} + frameBorder="0" + allowFullScreen + /> + </div> + ); +} + +/** + * Check if the iframe code in the post editor is to an allowed URL + * <iframe title="DTube embedded player" src="https://emb.d.tube/#!/lemwong/QmQqxBCkoVusMRwP6D9oBMRQdASFzABdKQxE7xLysfmsR6" width="640" height="360" frameborder="0" allowfullscreen=""></iframe> + * @param url + * @returns {boolean|*} + */ +export function validateIframeUrl(url) { + const match = url.match(regex.sanitize); + + if (match) { + return url; + } + + return false; +} + +/** + * Rewrites the embedded URL to a normalized format + * @param url + * @returns {string|boolean} + */ +export function normalizeEmbedUrl(url) { + const match = url.match(regex.contentId); + + if (match && match.length >= 2) { + return `https://emb.d.tube/#!/${match[1]}`; + } + + return false; +} + +/** + * Extract the content ID and other metadata from the URL + * @param data + * @returns {null|{id: *, canonical: string, url: *}} + */ +function extractContentId(data) { + if (!data) return null; + + const m = data.match(regex.main); + if (!m || m.length < 2) return null; + + return { + id: m[1], + url: m[0], + canonical: `https://emb.d.tube/#!/${m[1]}`, + }; +} + +/** + * Replaces the URL with a custom Markdown for embedded players + * @param child + * @param links + * @returns {*} + */ +export function embedNode(child, links /*images*/) { + try { + const data = child.data; + const dtube = extractContentId(data); + if (!dtube) return child; + + child.data = data.replace(dtube.url, `~~~ embed:${dtube.id} dtube ~~~`); + + if (links) links.add(dtube.canonical); + } catch (error) { + console.log(error); + } + + return child; +} diff --git a/src/app/components/elements/EmbeddedPlayers/index.jsx b/src/app/components/elements/EmbeddedPlayers/index.jsx new file mode 100644 index 0000000000000000000000000000000000000000..c69f4e087ac7b7c9714b77e9e7068527e92b45a2 --- /dev/null +++ b/src/app/components/elements/EmbeddedPlayers/index.jsx @@ -0,0 +1,230 @@ +import { + genIframeMd as genDtubeIframeMd, + validateIframeUrl as validateDtubeIframeUrl, + normalizeEmbedUrl as normalizeDtubeEmbedUrl, + embedNode as embedDtubeNode, +} from 'app/components/elements/EmbeddedPlayers/dtube'; + +import { + genIframeMd as genTwitchIframeMd, + validateIframeUrl as validateTwitchIframeUrl, + normalizeEmbedUrl as normalizeTwitchEmbedUrl, + embedNode as embedTwitchNode, +} from 'app/components/elements/EmbeddedPlayers/twitch'; + +import { validateIframeUrl as validateSoundcloudIframeUrl } from 'app/components/elements/EmbeddedPlayers/soundcloud'; + +import { + genIframeMd as genYoutubeIframeMd, + validateIframeUrl as validateYoutubeIframeUrl, + normalizeEmbedUrl as normalizeYoutubeEmbedUrl, + embedNode as embedYoutubeNode, +} from 'app/components/elements/EmbeddedPlayers/youtube'; + +import { + genIframeMd as genVimeoIframeMd, + validateIframeUrl as validateVimeoIframeUrl, + normalizeEmbedUrl as normalizeVimeoEmbedUrl, + embedNode as embedVimeoNode, +} from 'app/components/elements/EmbeddedPlayers/vimeo'; + +import { + genIframeMd as genThreespeakIframeMd, + validateIframeUrl as validateThreespeakIframeUrl, + normalizeEmbedUrl as normalizeThreespeakEmbedUrl, + embedNode as embedThreeSpeakNode, + preprocessHtml as preprocess3SpeakHtml, +} from 'app/components/elements/EmbeddedPlayers/threespeak'; + +const supportedProviders = [ + { + id: 'dtube', + validateIframeUrlFn: validateDtubeIframeUrl, + normalizeEmbedUrlFn: normalizeDtubeEmbedUrl, + embedNodeFn: embedDtubeNode, + genIframeMdFn: genDtubeIframeMd, + }, + { + id: 'twitch', + validateIframeUrlFn: validateTwitchIframeUrl, + normalizeEmbedUrlFn: normalizeTwitchEmbedUrl, + embedNodeFn: embedTwitchNode, + genIframeMdFn: genTwitchIframeMd, + }, + { + id: 'soundcloud', + validateIframeUrlFn: validateSoundcloudIframeUrl, + normalizeEmbedUrlFn: null, + embedNodeFn: null, + genIframeMdFn: null, + }, + { + id: 'youtube', + validateIframeUrlFn: validateYoutubeIframeUrl, + normalizeEmbedUrlFn: normalizeYoutubeEmbedUrl, + embedNodeFn: embedYoutubeNode, + genIframeMdFn: genYoutubeIframeMd, + }, + { + id: 'vimeo', + validateIframeUrlFn: validateVimeoIframeUrl, + normalizeEmbedUrlFn: normalizeVimeoEmbedUrl, + embedNodeFn: embedVimeoNode, + genIframeMdFn: genVimeoIframeMd, + }, + { + id: 'threespeak', + validateIframeUrlFn: validateThreespeakIframeUrl, + normalizeEmbedUrlFn: normalizeThreespeakEmbedUrl, + embedNodeFn: embedThreeSpeakNode, + genIframeMdFn: genThreespeakIframeMd, + }, +]; + +export default supportedProviders; + +/** + * Allow iFrame in the Markdown if the source URL is allowed + * @param url + * @returns {boolean|*} + */ +export function validateIframeUrl(url) { + for (let pi = 0; pi < supportedProviders.length; pi += 1) { + const provider = supportedProviders[pi]; + + const validIframeUrl = provider.validateIframeUrlFn(url); + + if (validIframeUrl !== false) { + console.log(`Found a valid ${provider.id} iframe URL`); + return validIframeUrl; + } + } + + return false; +} + +/** + * Rewrites the embedded URL to a normalized format + * @param url + * @returns {boolean|*} + */ +export function normalizeEmbedUrl(url) { + for (let pi = 0; pi < supportedProviders.length; pi += 1) { + const provider = supportedProviders[pi]; + + if (typeof provider.normalizeEmbedUrlFn === 'function') { + const validEmbedUrl = provider.normalizeEmbedUrlFn(url); + + if (validEmbedUrl !== false) { + console.log(`Found a valid ${provider.id} embedded URL`); + return validEmbedUrl; + } + } + } + + return false; +} + +/** + * Replaces the URL with a custom Markdown for embedded players + * @param child + * @param links + * @param images + * @returns {*} + */ +export function embedNode(child, links, images) { + for (let pi = 0; pi < supportedProviders.length; pi += 1) { + const provider = supportedProviders[pi]; + + if (typeof provider.embedNodeFn === 'function') { + child = provider.embedNodeFn(child, links, images); + } + } + + return child; +} + +/** + * Returns the provider config by ID + * @param id + * @returns {null|{normalizeEmbedUrlFn, validateIframeUrlFn, id: string, genIframeMdFn, embedNodeFn}|{normalizeEmbedUrlFn, validateIframeUrlFn, id: string, genIframeMdFn, embedNodeFn}|{normalizeEmbedUrlFn: null, validateIframeUrlFn, id: string, genIframeMdFn: null, embedNodeFn: null}|{normalizeEmbedUrlFn, validateIframeUrlFn, id: string, genIframeMdFn, embedNodeFn}|{normalizeEmbedUrlFn, validateIframeUrlFn, id: string, genIframeMdFn, embedNodeFn}} + */ +function getProviderById(id) { + for (let pi = 0; pi < supportedProviders.length; pi += 1) { + const provider = supportedProviders[pi]; + + if (provider.id === id) { + return provider; + } + } + + return null; +} + +/** + * Returns all providers IDs + * @returns {(string)[]} + */ +function getProviderIds() { + return supportedProviders.map(o => { + return o.id; + }); +} + +/** + * Replaces ~~~ embed: Markdown code to an iframe MD + * @param section + * @param idx + * @param large + * @returns {null|{markdown: null, section: string}} + */ +export function generateMd(section, idx, large) { + let markdown = null; + const supportedProvidersIds = getProviderIds(); + const regex = new RegExp( + `^([A-Za-z0-9\\?\\=\\_\\-\\/\\.]+) (${supportedProvidersIds.join( + '|' + )})\\s?(\\d+)? ~~~` + ); + const match = section.match(regex); + + if (match && match.length >= 3) { + const id = match[1]; + const type = match[2]; + const startTime = match[3] ? parseInt(match[3]) : 0; + const w = large ? 640 : 480, + h = large ? 360 : 270; + + const provider = getProviderById(type); + if (provider) { + markdown = provider.genIframeMdFn(idx, id, w, h, startTime); + } else { + console.error('MarkdownViewer unknown embed type', type); + } + + if (match[3]) { + section = section.substring( + `${id} ${type} ${startTime} ~~~`.length + ); + } else { + section = section.substring(`${id} ${type} ~~~`.length); + } + + return { + section, + markdown, + }; + } + + return null; +} + +/** + * Pre-process HTML codes from the Markdown before it gets transformed + * @param html + * @returns {*} + */ +export function preprocessHtml(html) { + html = preprocess3SpeakHtml(html); + return html; +} diff --git a/src/app/components/elements/EmbeddedPlayers/soundcloud.jsx b/src/app/components/elements/EmbeddedPlayers/soundcloud.jsx new file mode 100644 index 0000000000000000000000000000000000000000..2a72f6c32807e69f2474466ce12c0dde98c4ade0 --- /dev/null +++ b/src/app/components/elements/EmbeddedPlayers/soundcloud.jsx @@ -0,0 +1,27 @@ +/** + * Regular expressions for detecting and validating provider URLs + * @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}} + */ +const regex = { + sanitize: /^https:\/\/w.soundcloud.com\/player\/.*?url=(.+?)&.*/i, +}; + +export default regex; + +/** + * Check if the iframe code in the post editor is to an allowed URL + * <iframe width="100%" height="450" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/257659076&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true"></iframe> + * @param url + * @returns {boolean|*} + */ +export function validateIframeUrl(url) { + const match = url.match(regex.sanitize); + + if (!match || match.length !== 2) { + return false; + } + + return `https://w.soundcloud.com/player/?url=${ + match[1] + }&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true`; +} diff --git a/src/app/components/elements/EmbeddedPlayers/threespeak.jsx b/src/app/components/elements/EmbeddedPlayers/threespeak.jsx new file mode 100644 index 0000000000000000000000000000000000000000..448dbc440a4977e2b307d56f1f05eca78cd44616 --- /dev/null +++ b/src/app/components/elements/EmbeddedPlayers/threespeak.jsx @@ -0,0 +1,140 @@ +import React from 'react'; + +/** + * Regular expressions for detecting and validating provider URLs + * @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}} + */ +const regex = { + sanitize: /^https:\/\/3speak.online\/embed\?v=([A-Za-z0-9\_\-\/]+)(&.*)?$/, + main: /(?:https?:\/\/(?:(?:3speak.online\/watch\?v=)|(?:3speak.online\/embed\?v=)))([A-Za-z0-9\_\-\/]+)(&.*)?/i, + htmlReplacement: /<a href="(https?:\/\/3speak.online\/watch\?v=([A-Za-z0-9\_\-\/]+))".*<img.*?><\/a>/i, +}; + +export default regex; + +/** + * Generates the Markdown/HTML code to override the detected URL with an iFrame + * @param idx + * @param threespeakId + * @param w + * @param h + * @returns {*} + */ +export function genIframeMd(idx, threespeakId, w, h) { + const url = `https://3speak.online/embed?v=${threespeakId}`; + return ( + <div key={`threespeak-${threespeakId}-${idx}`} className="videoWrapper"> + <iframe + title="3Speak embedded player" + key={idx} + src={url} + width={w} + height={h} + frameBorder="0" + allowFullScreen + /> + </div> + ); +} + +/** + * Check if the iframe code in the post editor is to an allowed URL + * @param url + * @returns {boolean|*} + */ +export function validateIframeUrl(url) { + const match = url.match(regex.sanitize); + + if (match) { + return url; + } + + return false; +} + +/** + * Rewrites the embedded URL to a normalized format + * @param url + * @returns {string|boolean} + */ +export function normalizeEmbedUrl(url) { + const match = url.match(regex.contentId); + + if (match && match.length >= 2) { + return `https://3speak.online/embed?v=${match[1]}`; + } + + return false; +} + +/** + * Extract the content ID and other metadata from the URL + * @param data + * @returns {null|{id: *, canonical: string, url: *}} + */ +function extractContentId(data) { + if (!data) return null; + + const match = data.match(regex.main); + const url = match ? match[0] : null; + if (!url) return null; + const fullId = match[1]; + const id = fullId.split('/').pop(); + + return { + id, + fullId, + url, + thumbnail: `https://img.3speakcontent.online/${id}/post.png`, + }; +} + +/** + * Replaces the URL with a custom Markdown for embedded players + * @param child + * @param links + * @returns {*} + */ +export function embedNode(child, links /*images*/) { + try { + const data = child.data; + const threespeak = extractContentId(data); + if (!threespeak) return child; + + child.data = data.replace( + threespeak.url, + `~~~ embed:${threespeak.id} threespeak ~~~` + ); + + if (links) links.add(threespeak.canonical); + } catch (error) { + console.log(error); + } + + return child; +} + +/** + * Pre-process HTML codes from the Markdown before it gets transformed + * @param child + * @returns {string} + */ +export function preprocessHtml(child) { + try { + if (typeof child === 'string') { + // If typeof child is a string, this means we are trying to process the HTML + // to replace the image/anchor tag created by 3Speak dApp + const threespeak = extractContentId(child); + if (threespeak) { + child = child.replace( + regex.htmlReplacement, + `~~~ embed:${threespeak.fullId} threespeak ~~~` + ); + } + } + } catch (error) { + console.log(error); + } + + return child; +} diff --git a/src/app/components/elements/EmbeddedPlayers/twitch.jsx b/src/app/components/elements/EmbeddedPlayers/twitch.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4c93c61766044cc4c81e6a632de349a189523176 --- /dev/null +++ b/src/app/components/elements/EmbeddedPlayers/twitch.jsx @@ -0,0 +1,114 @@ +import React from 'react'; + +/** + * Regular expressions for detecting and validating provider URLs + * @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}} + */ +const regex = { + sanitize: /^(https?:)?\/\/player.twitch.tv\/.*/i, + main: /https?:\/\/(?:www.)?twitch.tv\/(?:(videos)\/)?([a-zA-Z0-9][\w]{3,24})/i, +}; + +export default regex; + +/** + * Check if the iframe code in the post editor is to an allowed URL + * <iframe src="https://player.twitch.tv/?channel=tfue" frameborder="0" allowfullscreen="true" scrolling="no" height="378" width="620"></iframe> + * @param url + * @returns {boolean|*} + */ +export function validateIframeUrl(url) { + const match = url.match(regex.sanitize); + + if (match) { + return url; + } + + return false; +} + +/** + * Rewrites the embedded URL to a normalized format + * @param url + * @returns {string|boolean} + */ +export function normalizeEmbedUrl(url) { + const match = url.match(regex.main); + + if (match && match.length >= 3) { + if (match[1] === undefined) { + return `https://player.twitch.tv/?autoplay=false&channel=${ + match[2] + }`; + } + + return `https://player.twitch.tv/?autoplay=false&video=${match[1]}`; + } + + return false; +} + +/** + * Extract the content ID and other metadata from the URL + * @param data + * @returns {null|{id: *, canonical: string, url: *}} + */ +function extractContentId(data) { + if (!data) return null; + + const m = data.match(regex.main); + + if (!m || m.length < 3) return null; + + return { + id: m[1] === `videos` ? `?video=${m[2]}` : `?channel=${m[2]}`, + url: m[0], + canonical: + m[1] === `videos` + ? `https://player.twitch.tv/?video=${m[2]}` + : `https://player.twitch.tv/?channel=${m[2]}`, + }; +} + +export function embedNode(child, links /*images*/) { + try { + const data = child.data; + const twitch = extractContentId(data); + if (!twitch) return child; + + child.data = data.replace( + twitch.url, + `~~~ embed:${twitch.id} twitch ~~~` + ); + + if (links) links.add(twitch.canonical); + } catch (error) { + console.error(error); + } + + return child; +} + +/** + * Generates the Markdown/HTML code to override the detected URL with an iFrame + * @param idx + * @param threespeakId + * @param w + * @param h + * @returns {*} + */ +export function genIframeMd(idx, id, w, h) { + const url = `https://player.twitch.tv/${id}`; + return ( + <div key={`twitch-${id}-${idx}`} className="videoWrapper"> + <iframe + title="Twitch embedded player" + src={url} + width={w} + height={h} + frameBorder="0" + allowFullScreen + /> + </div> + ); +} diff --git a/src/app/components/elements/EmbeddedPlayers/vimeo.jsx b/src/app/components/elements/EmbeddedPlayers/vimeo.jsx new file mode 100644 index 0000000000000000000000000000000000000000..16be8871d7a5e0bfd7633215f11b70af7c7a858e --- /dev/null +++ b/src/app/components/elements/EmbeddedPlayers/vimeo.jsx @@ -0,0 +1,124 @@ +import React from 'react'; + +/** + * Regular expressions for detecting and validating provider URLs + * @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}} + */ +const regex = { + sanitize: /^(https?:)?\/\/player.vimeo.com\/video\/([0-9]*)/i, + main: /https?:\/\/(?:vimeo.com\/|player.vimeo.com\/video\/)([0-9]+)\/?(#t=((\d+)s?))?\/?/, + contentId: /(?:vimeo.com\/|player.vimeo.com\/video\/)([0-9]+)/, +}; + +export default regex; + +/** + * Check if the iframe code in the post editor is to an allowed URL + * <iframe src="https://player.vimeo.com/video/179213493" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> + * @param url + * @returns {boolean|*} + */ +export function validateIframeUrl(url) { + const match = url.match(regex.sanitize); + + if (!match || match.length !== 3) { + return false; + } + + return 'https://player.vimeo.com/video/' + match[2]; +} + +/** + * Rewrites the embedded URL to a normalized format + * @param url + * @returns {string|boolean} + */ +export function normalizeEmbedUrl(url) { + const match = url.match(regex.contentId); + + if (match && match.length >= 2) { + return `https://player.vimeo.com/video/${match[1]}`; + } + + return false; +} + +/** + * Extract the content ID and other metadata from the URL + * @param data + * @returns {null|{id: *, canonical: string, url: *}} + */ +function extractContentId(data) { + if (!data) return null; + const m = data.match(regex.main); + if (!m || m.length < 2) return null; + + const startTime = m.input.match(/t=(\d+)s?/); + + return { + id: m[1], + url: m[0], + startTime: startTime ? startTime[1] : 0, + canonical: `https://player.vimeo.com/video/${m[1]}`, + // thumbnail: requires a callback - http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo + }; +} + +/** + * Replaces the URL with a custom Markdown for embedded players + * @param child + * @param links + * @returns {*} + */ +export function embedNode(child, links /*images*/) { + try { + const data = child.data; + const vimeo = extractContentId(data); + if (!vimeo) return child; + + const vimeoRegex = new RegExp(`${vimeo.url}(#t=${vimeo.startTime}s?)?`); + if (vimeo.startTime > 0) { + child.data = data.replace( + vimeoRegex, + `~~~ embed:${vimeo.id} vimeo ${vimeo.startTime} ~~~` + ); + } else { + child.data = data.replace( + vimeoRegex, + `~~~ embed:${vimeo.id} vimeo ~~~` + ); + } + + if (links) links.add(vimeo.canonical); + // if(images) images.add(vimeo.thumbnail) // not available + } catch (error) { + console.log(error); + } + return child; +} + +/** + * Generates the Markdown/HTML code to override the detected URL with an iFrame + * @param idx + * @param threespeakId + * @param w + * @param h + * @returns {*} + */ +export function genIframeMd(idx, id, w, h, startTime) { + const url = `https://player.vimeo.com/video/${id}#t=${startTime}s`; + return ( + <div key={`vimeo-${id}-${idx}`} className="videoWrapper"> + <iframe + title="Vimeo embedded player" + src={url} + width={w} + height={h} + frameBorder="0" + webkitallowfullscreen + mozallowfullscreen + allowFullScreen + /> + </div> + ); +} diff --git a/src/app/components/elements/EmbeddedPlayers/youtube.jsx b/src/app/components/elements/EmbeddedPlayers/youtube.jsx new file mode 100644 index 0000000000000000000000000000000000000000..973ba5c49825e3b1895894d6d758c827cf2ef569 --- /dev/null +++ b/src/app/components/elements/EmbeddedPlayers/youtube.jsx @@ -0,0 +1,130 @@ +import React from 'react'; +import YoutubePreview from 'app/components/elements/YoutubePreview'; + +/** + * Regular expressions for detecting and validating provider URLs + * @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}} + */ +const regex = { + sanitize: /^(https?:)?\/\/www.youtube.com\/embed\/.*/i, + //main: new RegExp(urlSet({ domain: '(?:(?:.*.)?youtube.com|youtu.be)' }), flags), + main: /(?:https?:\/\/)(?:www\.)?(?:(?:youtube.com\/watch\?v=)|(?:youtu.be\/)|(?:youtube.com\/embed\/))([A-Za-z0-9\_\-]+)[^ ]*/i, + contentId: /(?:(?:youtube.com\/watch\?v=)|(?:youtu.be\/)|(?:youtube.com\/embed\/))([A-Za-z0-9\_\-]+)/i, +}; + +export default regex; + +/** + * Check if the iframe code in the post editor is to an allowed URL + * <iframe width="560" height="315" src="https://www.youtube.com/embed/KOnk7Nbqkhs" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + * @param url + * @returns {boolean|*} + */ +export function validateIframeUrl(url) { + const match = url.match(regex.sanitize); + + if (match) { + // strip query string (yt: autoplay=1,controls=0,showinfo=0, etc) + return url.replace(/\?.+$/, ''); + } + + return false; +} + +/** + * Rewrites the embedded URL to a normalized format + * @param url + * @returns {string|boolean} + */ +export function normalizeEmbedUrl(url) { + const match = url.match(regex.contentId); + + if (match && match.length >= 2) { + return `https://youtube.com/embed/${match[1]}`; + } + + return false; +} + +/** + * Extract the content ID and other metadata from the URL + * @param data + * @returns {null|{id: *, canonical: string, url: *}} + */ +export function extractContentId(data) { + if (!data) return null; + + const m1 = data.match(regex.main); + const url = m1 ? m1[0] : null; + + if (!url) return null; + + const m2 = url.match(regex.contentId); + const id = m2 && m2.length >= 2 ? m2[1] : null; + + if (!id) return null; + + const startTime = url.match(/t=(\d+)s?/); + + return { + id, + url, + startTime: startTime ? startTime[1] : 0, + thumbnail: 'https://img.youtube.com/vi/' + id + '/0.jpg', + }; +} + +/** + * Replaces the URL with a custom Markdown for embedded players + * @param child + * @param links + * @returns {*} + */ +export function embedNode(child, links, images) { + try { + const yt = extractContentId(child.data); + + if (!yt) return child; + + if (yt.startTime) { + child.data = child.data.replace( + yt.url, + `~~~ embed:${yt.id} youtube ${yt.startTime} ~~~` + ); + } else { + child.data = child.data.replace( + yt.url, + `~~~ embed:${yt.id} youtube ~~~` + ); + } + + if (links) links.add(yt.url); + if (images) images.add(yt.thumbnail); + } catch (error) { + console.log(error); + } + + return child; +} + +/** + * Generates the Markdown/HTML code to override the detected URL with an iFrame + * @param idx + * @param threespeakId + * @param w + * @param h + * @returns {*} + */ +export function genIframeMd(idx, id, w, h, startTime) { + return ( + <YoutubePreview + key={`youtube-${id}-${idx}`} + width={w} + height={h} + youTubeId={id} + startTime={startTime} + frameBorder="0" + allowFullScreen="true" + /> + ); +} diff --git a/src/app/components/elements/ReplyEditor.jsx b/src/app/components/elements/ReplyEditor.jsx index 797b3c38188ffa4754c3019e12079265cf93665b..6a6183488c1e279772612f00d4c546931bc74e26 100644 --- a/src/app/components/elements/ReplyEditor.jsx +++ b/src/app/components/elements/ReplyEditor.jsx @@ -25,6 +25,7 @@ import { fromJS, Set, OrderedSet } from 'immutable'; import Remarkable from 'remarkable'; import Dropzone from 'react-dropzone'; import tt from 'counterpart'; +import { loadUserTemplates, saveUserTemplates } from 'app/utils/UserTemplates'; const remarkable = new Remarkable({ html: true, linkify: false, breaks: true }); @@ -75,6 +76,7 @@ class ReplyEditor extends React.Component { body: PropTypes.string, // initial value defaultPayoutType: PropTypes.string, payoutType: PropTypes.string, + postTemplateName: PropTypes.string, }; static defaultProps = { @@ -191,6 +193,70 @@ class ReplyEditor extends React.Component { const tp = this.props; const np = nextProps; + if ( + typeof nextProps.postTemplateName !== 'undefined' && + nextProps.postTemplateName !== null + ) { + const { formId } = tp; + + if (nextProps.postTemplateName.indexOf('create_') === 0) { + const { username } = tp; + const { body, title, tags } = ns; + const { payoutType, beneficiaries } = np; + const userTemplates = loadUserTemplates(username); + const newTemplateName = nextProps.postTemplateName.replace( + 'create_', + '' + ); + const newTemplate = { + name: nextProps.postTemplateName.replace('create_', ''), + beneficiaries, + payoutType, + markdown: body !== undefined ? body.value : '', + title: title !== undefined ? title.value : '', + tags: tags !== undefined ? tags.value : '', + }; + + let updated = false; + for (let ui = 0; ui < userTemplates.length; ui += 1) { + if (userTemplates[ui].name === newTemplateName) { + userTemplates[ui] = newTemplate; + updated = true; + } + } + + if (updated === false) { + userTemplates.push(newTemplate); + } + + saveUserTemplates(username, userTemplates); + + this.props.setPostTemplateName(formId, null); + } else { + const userTemplates = loadUserTemplates(nextProps.username); + + for (let ti = 0; ti < userTemplates.length; ti += 1) { + const template = userTemplates[ti]; + if (template.name === nextProps.postTemplateName) { + this.state.body.props.onChange(template.markdown); + this.state.title.props.onChange(template.title); + this.state.tags.props.onChange(template.tags); + this.props.setPayoutType( + formId, + template.payoutType + ); + this.props.setBeneficiaries( + formId, + template.beneficiaries + ); + + this.props.setPostTemplateName(formId, null); + break; + } + } + } + } + // Save curent draft to localStorage if ( ts.body.value !== ns.body.value || @@ -1024,6 +1090,12 @@ export default formId => formId, 'beneficiaries', ]); + const postTemplateName = state.user.getIn([ + 'current', + 'post', + formId, + 'postTemplateName', + ]); beneficiaries = beneficiaries ? beneficiaries.toJS() : []; // Post full @@ -1056,6 +1128,7 @@ export default formId => defaultPayoutType, payoutType, beneficiaries, + postTemplateName, initialValues: { title, body, tags }, formId, }; @@ -1081,6 +1154,13 @@ export default formId => value: fromJS(beneficiaries), }) ), + setPostTemplateName: (formId, postTemplateName) => + dispatch( + userActions.set({ + key: ['current', 'post', formId, 'postTemplateName'], + value: postTemplateName, + }) + ), reply: ({ tags, title, diff --git a/src/app/components/elements/SortOrder/index.jsx b/src/app/components/elements/SortOrder/index.jsx index 050cd58edcc8fce79c05e0da12789b937700f26b..8a6f455e3b0dfdf6ab471ebc50fe6bfc6434b60b 100644 --- a/src/app/components/elements/SortOrder/index.jsx +++ b/src/app/components/elements/SortOrder/index.jsx @@ -26,11 +26,11 @@ const SortOrder = ({ topic, sortOrder, horizontal, pathname }) => { { label: tt('main_menu.trending'), value: `/trending${tag}`, - } /* + }, { label: tt('main_menu.hot'), value: `/hot${tag}`, - },*/, + }, ]; if (!topMenu) { diff --git a/src/app/components/modules/PostAdvancedSettings.jsx b/src/app/components/modules/PostAdvancedSettings.jsx index a94e730914bc0fef148b2ab931ef2f6c6fb9d70d..b9ec90aa86d84c87ef174c236efcbeab94463213 100644 --- a/src/app/components/modules/PostAdvancedSettings.jsx +++ b/src/app/components/modules/PostAdvancedSettings.jsx @@ -4,8 +4,11 @@ import reactForm from 'app/utils/ReactForm'; import { SUBMIT_FORM_ID } from 'shared/constants'; import tt from 'counterpart'; import { fromJS } from 'immutable'; -import BeneficiarySelector from 'app/components/cards/BeneficiarySelector'; -import { validateBeneficiaries } from 'app/components/cards/BeneficiarySelector'; +import BeneficiarySelector, { + validateBeneficiaries, +} from 'app/components/cards/BeneficiarySelector'; +import PostTemplateSelector from 'app/components/cards/PostTemplateSelector'; +import { loadUserTemplates, saveUserTemplates } from 'app/utils/UserTemplates'; import * as userActions from 'app/redux/UserReducer'; @@ -16,7 +19,10 @@ class PostAdvancedSettings extends Component { constructor(props) { super(); - this.state = { payoutType: props.initialPayoutType }; + this.state = { + payoutType: props.initialPayoutType, + postTemplateName: null, + }; this.initForm(props); } @@ -43,6 +49,52 @@ class PostAdvancedSettings extends Component { this.setState({ payoutType: event.target.value }); }; + handleTemplateSelected = postTemplateName => { + const { username } = this.props; + const userTemplates = loadUserTemplates(username); + this.setState({ postTemplateName }); + + if (postTemplateName !== null) { + for (let ti = 0; ti < userTemplates.length; ti += 1) { + const template = userTemplates[ti]; + const { beneficiaries } = this.state; + const newBeneficiaries = { + ...beneficiaries, + }; + + if (template.name === postTemplateName) { + if (template.hasOwnProperty('payoutType')) { + this.setState({ payoutType: template.payoutType }); + } + + if (template.hasOwnProperty('beneficiaries')) { + newBeneficiaries.props.value = template.beneficiaries; + this.setState({ beneficiaries: newBeneficiaries }); + } + + break; + } + } + } + }; + + handleDeleteTemplate = (event, postTemplateName) => { + event.preventDefault(); + event.stopPropagation(); + + const { username } = this.props; + const userTemplates = loadUserTemplates(username); + let ui = userTemplates.length; + while (ui--) { + if (userTemplates[ui].name === postTemplateName) { + userTemplates.splice(ui, 1); + } + } + + saveUserTemplates(username, [...userTemplates]); + this.setState({ postTemplateName: null }); + }; + render() { const { formId, @@ -50,10 +102,18 @@ class PostAdvancedSettings extends Component { defaultPayoutType, initialPayoutType, } = this.props; - const { beneficiaries, payoutType } = this.state; + const { beneficiaries, payoutType, postTemplateName } = this.state; + const loadingTemplate = + postTemplateName && postTemplateName.indexOf('create_') === -1; const { submitting, valid, handleSubmit } = this.state.advancedSettings; + const userTemplates = loadUserTemplates(username); const disabled = - submitting || !(valid || payoutType !== initialPayoutType); + submitting || + !( + valid || + payoutType !== initialPayoutType || + postTemplateName !== null + ); const form = ( <form @@ -66,6 +126,10 @@ class PostAdvancedSettings extends Component { if (!err) { this.props.setPayoutType(formId, payoutType); this.props.setBeneficiaries(formId, data.beneficiaries); + this.props.setPostTemplateName( + formId, + postTemplateName + ); this.props.hideAdvancedSettings(); } })} @@ -87,7 +151,7 @@ class PostAdvancedSettings extends Component { <div className="row"> <div className="small-12 medium-6 large-12 columns"> <select - defaultValue={payoutType} + value={payoutType} onChange={this.handlePayoutChange} > <option value="0%"> @@ -129,6 +193,11 @@ class PostAdvancedSettings extends Component { </h4> </div> <BeneficiarySelector {...beneficiaries.props} tabIndex={1} /> + <PostTemplateSelector + username={username} + onChange={this.handleTemplateSelected} + templates={userTemplates} + /> <div className="error"> {(beneficiaries.touched || beneficiaries.value) && beneficiaries.error} @@ -142,8 +211,31 @@ class PostAdvancedSettings extends Component { disabled={disabled} tabIndex={2} > - {tt('g.save')} + {loadingTemplate && + tt( + 'post_advanced_settings_jsx.load_template' + )} + {!loadingTemplate && tt('g.save')} </button> + {loadingTemplate && ( + <button + className="button" + tabIndex={2} + onClick={event => { + this.handleDeleteTemplate( + event, + postTemplateName + ); + }} + > + {postTemplateName && + postTemplateName.indexOf('create_') === + -1 && + tt( + 'post_advanced_settings_jsx.delete_template' + )} + </button> + )} </span> </div> </div> @@ -217,5 +309,14 @@ export default connect( value: fromJS(beneficiaries), }) ), + setPostTemplateName: (formId, postTemplateName, create = false) => + dispatch( + userActions.set({ + key: ['current', 'post', formId, 'postTemplateName'], + value: create + ? `create_${postTemplateName}` + : postTemplateName, + }) + ), }) )(PostAdvancedSettings); diff --git a/src/app/components/modules/Settings.jsx b/src/app/components/modules/Settings.jsx index 3fc2997d5ba9d499ac1ccb281375df7e24e300e4..19c8df907943188cf53c9d9f3d35de7c1ee5dfea 100644 --- a/src/app/components/modules/Settings.jsx +++ b/src/app/components/modules/Settings.jsx @@ -10,6 +10,7 @@ import reactForm from 'app/utils/ReactForm'; import Dropzone from 'react-dropzone'; import MuteList from 'app/components/elements/MuteList'; import { isLoggedIn } from 'app/utils/UserUtil'; +import * as api from '@hiveio/hive-js'; class Settings extends React.Component { constructor(props) { @@ -232,6 +233,36 @@ class Settings extends React.Component { this.props.setUserPreferences(userPreferences); }; + generateAPIEndpointOptions = () => { + let endpoints = api.config.get('alternative_api_endpoints'); + let preferred_api_endpoint = ''; + if (typeof window !== 'undefined') + preferred_api_endpoint = + localStorage.getItem('user_preferred_api_endpoint') === null + ? 'https://api.hive.blog' + : localStorage.getItem('user_preferred_api_endpoint'); + if (endpoints === null || endpoints === undefined) { + return null; + } + let entries = []; + for (var endpoint of endpoints) { + if (endpoint === preferred_api_endpoint) continue; //this one is always present even if the api config call fails + let entry = <option value={endpoint}>{endpoint}</option>; + entries.push(entry); + } + return entries; + }; + + handlePreferredAPIEndpointChange = event => { + if (typeof window !== 'undefined') { + localStorage.setItem( + 'user_preferred_api_endpoint', + event.target.value + ); + api.api.setOptions({ url: event.target.value }); + } + }; + render() { const { state, props } = this; const { @@ -261,6 +292,14 @@ class Settings extends React.Component { progress, } = this.state; + let preferred_api_endpoint = 'https://api.hive.blog'; + if (typeof window !== 'undefined') { + preferred_api_endpoint = + localStorage.getItem('user_preferred_api_endpoint') === null + ? 'https://api.hive.blog' + : localStorage.getItem('user_preferred_api_endpoint'); + } + return ( <div className="Settings"> <div className="row"> @@ -511,6 +550,24 @@ class Settings extends React.Component { </option> </select> </label> + + <label> + {tt( + 'settings_jsx.choose_preferred_api_endpoint' + )} + <select + defaultValue={preferred_api_endpoint} + onChange={ + this.handlePreferredAPIEndpointChange + } + > + <option value={preferred_api_endpoint}> + {preferred_api_endpoint} + </option> + + {this.generateAPIEndpointOptions()} + </select> + </label> <br /> </div> </div> diff --git a/src/app/components/modules/SidePanel/index.jsx b/src/app/components/modules/SidePanel/index.jsx index 2b449b62401396a765e9e3c635b6d43c49acd175..8e6bfef20b3c93c21e620a2e999d4d6fd1199734 100644 --- a/src/app/components/modules/SidePanel/index.jsx +++ b/src/app/components/modules/SidePanel/index.jsx @@ -122,7 +122,7 @@ const SidePanel = ({ organizational: [ { label: tt('navigation.api_docs'), - link: 'https://developers.hive.blog/', + link: 'https://developers.hive.io/', }, { label: tt('navigation.bluepaper'), diff --git a/src/app/locales/en.json b/src/app/locales/en.json index 1e593e74e272d1074806190a6fb35cfcc3a47267..81e6fb01a88ed93ba3b0012adcb3a8b160e0e70c 100644 --- a/src/app/locales/en.json +++ b/src/app/locales/en.json @@ -361,6 +361,15 @@ "beneficiary_percent_total_invalid": "Beneficiary total percentage must be less than 100" }, + "post_template_selector_jsx": { + "templates": "Post templates", + "templates_description": + "You can create post templates locally and load them to create a new blog post. All advanced settings will also be saved and loaded.", + "choose_template": "-- Choose a template to load --", + "create_template_first": "Please create a template first", + "new_template_name": "Name of a new template", + "template_saved": "Template saved successfully" + }, "category_selector_jsx": { "tag_your_story": "Tag (up to 8 tags), the first tag is your main category.", @@ -383,7 +392,9 @@ "payout_option_description": "What type of tokens do you want as rewards from this post?", "current_default": "Default", - "update_default_in_settings": "Update" + "update_default_in_settings": "Update", + "load_template": "Load template", + "delete_template": "Delete template" }, "postfull_jsx": { "this_post_is_not_available_due_to_a_copyright_claim": @@ -712,7 +723,8 @@ "profile_about": "About", "profile_location": "Location", "profile_website": "Website", - "saved": "Saved!" + "saved": "Saved!", + "choose_preferred_api_endpoint": "Choose Your Preferred API Node" }, "transfer_jsx": { "amount_is_in_form": "Amount is in the form 99999.999", diff --git a/src/app/locales/es.json b/src/app/locales/es.json index dac20e857995b4de88cbccb672f3e94e0ae2d008..181d104dc4ec07f5a865a4957879c0a2bb7e19fe 100644 --- a/src/app/locales/es.json +++ b/src/app/locales/es.json @@ -623,7 +623,8 @@ "profile_name": "Nombre de visualización", "profile_about": "Sobre", "profile_location": "Localización", - "profile_website": "Página Web" + "profile_website": "Página Web", + "choose_preferred_api_endpoint": "Elija su nodo de API preferido" }, "transfer_jsx": { "amount_is_in_form": "La cantidad está en el formato 99999.999", diff --git a/src/app/locales/fr.json b/src/app/locales/fr.json index f9f5768df1a425fa04418b6d773bf4f692d2c166..7428497c64be2df02aefc16781aebeddd0f62ea7 100644 --- a/src/app/locales/fr.json +++ b/src/app/locales/fr.json @@ -645,7 +645,8 @@ "profile_name": "Nom à afficher", "profile_about": "A propos", "profile_location": "Lieu", - "profile_website": "Site internet" + "profile_website": "Site internet", + "choose_preferred_api_endpoint": "Choisissez votre nÅ“ud d'API préféré" }, "transfer_jsx": { "amount_is_in_form": "Montant dans le format 99999.999", diff --git a/src/app/locales/it.json b/src/app/locales/it.json index dfb13a0df1a2b254dceba8d187d8daba20faf91e..83dbb5dcf5178826521b141849786cbdbbba885a 100644 --- a/src/app/locales/it.json +++ b/src/app/locales/it.json @@ -630,7 +630,8 @@ "profile_name": "Mostra nome", "profile_about": "About", "profile_location": "Località ", - "profile_website": "Sito" + "profile_website": "Sito", + "choose_preferred_api_endpoint": "Scegli il tuo nodo API preferito" }, "transfer_jsx": { "amount_is_in_form": "La quantita deve essere della forma 99999.999", diff --git a/src/app/locales/ja.json b/src/app/locales/ja.json index 076c3f5140a31269edf3de78a84b7c57519f98fa..697d657b7b0a8e0ec61ac334aa17a76f604dbf59 100644 --- a/src/app/locales/ja.json +++ b/src/app/locales/ja.json @@ -627,7 +627,8 @@ "profile_name": "表示å", "profile_about": "概è¦", "profile_location": "å ´æ‰€", - "profile_website": "ウェブサイト" + "profile_website": "ウェブサイト", + "choose_preferred_api_endpoint": "優先APIãƒŽãƒ¼ãƒ‰ã‚’é¸æŠžã—ã¦ãã ã•ã„" }, "transfer_jsx": { "amount_is_in_form": "金é¡ã¯99999.999ã®å½¢å¼ã§ã™", diff --git a/src/app/locales/ko.json b/src/app/locales/ko.json index d2e4037d6d45057ecae6105727cbdc33a8610f36..5f338219c139b424f763b39bede55ef06e1a1b1f 100644 --- a/src/app/locales/ko.json +++ b/src/app/locales/ko.json @@ -625,7 +625,8 @@ "profile_name": "닉네임", "profile_about": "한 줄 소개", "profile_location": "ì§€ì—", - "profile_website": "웹사ì´íЏ" + "profile_website": "웹사ì´íЏ", + "choose_preferred_api_endpoint": "기본 API 노드 ì„ íƒ" }, "transfer_jsx": { "amount_is_in_form": "Amount is in the form 99999.999", diff --git a/src/app/locales/pl.json b/src/app/locales/pl.json index 926526cc53e739f240f225f83b692ff03021bf12..bcc6b05475f2dc4c8c573659ffe7d853c4f73107 100644 --- a/src/app/locales/pl.json +++ b/src/app/locales/pl.json @@ -724,7 +724,8 @@ "profile_about": "Opis", "profile_location": "Lokalizacja", "profile_website": "Strona www", - "saved": "Zapisano!" + "saved": "Zapisano!", + "choose_preferred_api_endpoint": "Wybierz preferowany wÄ™zeÅ‚ API" }, "transfer_jsx": { "amount_is_in_form": "Podana kwota musi być w formacie 99999.999", diff --git a/src/app/locales/ru.json b/src/app/locales/ru.json index 18ddc273bec9934c326c54f3e6d5b6e9aa3e0ff8..b9de6fdb0979916f66f3d6755c5c65560885cb0d 100644 --- a/src/app/locales/ru.json +++ b/src/app/locales/ru.json @@ -648,7 +648,9 @@ "profile_name": "Отображаемое имÑ", "profile_about": "О Ñебе", "profile_location": "МеÑтоположение", - "profile_website": "Веб-Ñайт" + "profile_website": "Веб-Ñайт", + "choose_preferred_api_endpoint": + "Выберите Ñвой предпочтительный узел API" }, "transfer_jsx": { "amount_is_in_form": "Сумма должна быть в формате 99999.999", diff --git a/src/app/locales/zh.json b/src/app/locales/zh.json index 4403d15b0be646e45ed940b7450461878a0f6051..856ff7b44fa29ecb65a6a5f9e4ed497731be6181 100644 --- a/src/app/locales/zh.json +++ b/src/app/locales/zh.json @@ -574,7 +574,8 @@ "profile_name": "显示åç§°", "profile_about": "关于", "profile_location": "ä½ç½®", - "profile_website": "网站" + "profile_website": "网站", + "choose_preferred_api_endpoint": "选择您的首选API节点" }, "transfer_jsx": { "amount_is_in_form": "金é¢ä»¥99999.999的形å¼å‡ºçް", diff --git a/src/app/utils/BadActorList.js b/src/app/utils/BadActorList.js index e6730008319672894e6148d331d1d9dc7f43f828..86b6676f2071767e9184f4baa8da5517d1442f96 100644 --- a/src/app/utils/BadActorList.js +++ b/src/app/utils/BadActorList.js @@ -180,9 +180,7 @@ blocktraders blocktrades-com blocktrades-info blocktrades-us -blocktrades.com blocktrades.info -blocktrades.us blocktradess blocktradesss blocktradez diff --git a/src/app/utils/ExtractMeta.js b/src/app/utils/ExtractMeta.js index 1c944023607ee889a8bcbd4b246ee7f743c3aea2..c5d169c92cbe0c59a5a1bda0db88078b16dedc44 100644 --- a/src/app/utils/ExtractMeta.js +++ b/src/app/utils/ExtractMeta.js @@ -1,6 +1,8 @@ import { extractBodySummary, extractImageLink } from 'app/utils/ExtractContent'; -import { objAccessor } from 'app/utils/Accessors'; import { makeCanonicalLink } from 'app/utils/CanonicalLinker.js'; +import { proxifyImageUrl } from 'app/utils/ProxifyUrl'; + +const proxify = (url, size) => proxifyImageUrl(url, size).replace(/ /g, '%20'); const site_desc = 'Communities without borders. A social network owned and operated by its users, powered by Hive.'; @@ -51,7 +53,9 @@ function addPostMeta(metas, content, profile) { metas.push({ name: 'og:url', content: localUrl }); metas.push({ name: 'og:image', - content: image || 'https://hive.blog/images/hive-blog-share.png', + content: + proxify(image, '1200x630') || + 'https://hive.blog/images/hive-blog-share.png', }); metas.push({ name: 'og:description', content: desc }); metas.push({ name: 'og:site_name', content: 'Hive' }); @@ -72,7 +76,9 @@ function addPostMeta(metas, content, profile) { metas.push({ name: 'twitter:description', content: desc }); metas.push({ name: 'twitter:image', - content: image || 'https://hive.blog/images/hive-blog-twshare.png', + content: + proxify(image, '1200x630') || + 'https://hive.blog/images/hive-blog-twshare.png', }); } @@ -102,7 +108,6 @@ function addAccountMeta(metas, accountname, profile) { } function readProfile(chain_data, account) { - const profiles = chain_data.profiles; if (!chain_data.profiles[account]) return {}; return chain_data.profiles[account]['metadata']['profile']; } diff --git a/src/app/utils/Links.js b/src/app/utils/Links.js index 164255752f56ca54242b68e11f2e25231929077c..5d75cec64fcf744856cc3045aee8981089550eb7 100644 --- a/src/app/utils/Links.js +++ b/src/app/utils/Links.js @@ -28,8 +28,6 @@ export const remote = (flags = 'i') => urlSet({ domain: `(?!localhost|(?:.*\\.)?steemit.com)${domainPath}` }), flags ); -export const youTube = (flags = 'i') => - new RegExp(urlSet({ domain: '(?:(?:.*.)?youtube.com|youtu.be)' }), flags); export const image = (flags = 'i') => new RegExp(urlSet({ path: imagePath }), flags); export const imageFile = (flags = 'i') => new RegExp(imagePath, flags); @@ -42,17 +40,6 @@ export default { remote: remote(), image: image(), imageFile: imageFile(), - youTube: youTube(), - youTubeId: /(?:(?:youtube.com\/watch\?v=)|(?:youtu.be\/)|(?:youtube.com\/embed\/))([A-Za-z0-9\_\-]+)/i, - vimeo: /https?:\/\/(?:vimeo.com\/|player.vimeo.com\/video\/)([0-9]+)\/?(#t=((\d+)s?))?\/?/, - vimeoId: /(?:vimeo.com\/|player.vimeo.com\/video\/)([0-9]+)/, - // simpleLink: new RegExp(`<a href="(.*)">(.*)<\/a>`, 'ig'), - ipfsPrefix: /(https?:\/\/.*)?\/ipfs/i, - twitch: /https?:\/\/(?:www.)?twitch.tv\/(?:(videos)\/)?([a-zA-Z0-9][\w]{3,24})/i, - dtube: /https:\/\/(?:emb\.)?(?:d.tube\/\#\!\/(?:v\/)?)([a-zA-Z0-9\-\.\/]*)/, - dtubeId: /(?:d\.tube\/#!\/(?:v\/)?([a-zA-Z0-9\-\.\/]*))+/, - threespeak: /(?:https?:\/\/(?:(?:3speak.online\/watch\?v=)|(?:3speak.online\/embed\?v=)))([A-Za-z0-9\_\-\/]+)(&.*)?/i, - threespeakImageLink: /<a href="(https?:\/\/3speak.online\/watch\?v=([A-Za-z0-9\_\-\/]+))".*<img.*?><\/a>/i, }; //TODO: possible this should go somewhere else. diff --git a/src/app/utils/Links.test.js b/src/app/utils/Links.test.js index 4f9d494bdf7bc9ff0b0bbb1b5ad3bca113b436e7..8fea49b49797d4f991d7e8cb3cfb25d729301db8 100644 --- a/src/app/utils/Links.test.js +++ b/src/app/utils/Links.test.js @@ -2,6 +2,8 @@ import assert from 'assert'; import secureRandom from 'secure-random'; import links, * as linksRe from 'app/utils/Links'; import { PARAM_VIEW_MODE, VIEW_MODE_WHISTLE } from '../../shared/constants'; +import youtubeRegex from 'app/components/elements/EmbeddedPlayers/youtube'; +import threespeakRegex from 'app/components/elements/EmbeddedPlayers/threespeak'; describe('Links', () => { it('all', () => { @@ -241,31 +243,31 @@ describe('Performance', () => { } }); it('youTube', () => { - match(linksRe.youTube(), 'https://youtu.be/xG7ajrbj4zs?t=7s'); + match(youtubeRegex.main, 'https://youtu.be/xG7ajrbj4zs?t=7s'); match( - linksRe.youTube(), + youtubeRegex.main, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&t=14s' ); match( - linksRe.youTube(), + youtubeRegex.main, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&feature=youtu.be&t=14s' ); }); it('youTubeId', () => { match( - links.youTubeId, + youtubeRegex.contentId, 'https://youtu.be/xG7ajrbj4zs?t=7s', 'xG7ajrbj4zs', 1 ); match( - links.youTubeId, + youtubeRegex.contentId, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&t=14s', 'xG7ajrbj4zs', 1 ); match( - links.youTubeId, + youtubeRegex.contentId, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&feature=youtu.be&t=14s', 'xG7ajrbj4zs', 1 @@ -273,33 +275,33 @@ describe('Performance', () => { }); it('threespeak', () => { match( - links.threespeak, + threespeakRegex.main, 'https://3speak.online/watch?v=artemislives/tvxkobat' ); match( - links.threespeak, + threespeakRegex.main, 'https://3speak.online/watch?v=artemislives/tvxkobat&jwsource=cl' ); match( - links.threespeak, + threespeakRegex.main, 'https://3speak.online/embed?v=artemislives/tvxkobat' ); }); it('threespeakId', () => { match( - links.threespeak, + threespeakRegex.main, 'https://3speak.online/watch?v=artemislives/tvxkobat', 'artemislives/tvxkobat', 1 ); match( - links.threespeak, + threespeakRegex.main, 'https://3speak.online/watch?v=artemislives/tvxkobat&jwsource=cl', 'artemislives/tvxkobat', 1 ); match( - links.threespeak, + threespeakRegex.main, 'https://3speak.online/embed?v=artemislives/tvxkobat', 'artemislives/tvxkobat', 1 @@ -307,8 +309,8 @@ describe('Performance', () => { }); it('threespeakImageLink', () => { match( - links.threespeakImageLink, - '<a href="https://3speak.online/watch?v=artemislives/tvxkobat" rel="noopener" title="This link will take you away from steemit.com"><img src="https://steemitimages.com/640x0/https://img.3speakcontent.online/tvxkobat/post.png"></a>' + threespeakRegex.htmlReplacement, + '<a href="https://3speak.online/watch?v=artemislives/tvxkobat" rel="noopener" title="This link will take you away from steemit.com" class="steem-keychain-checked"><img src="https://steemitimages.com/640x0/https://img.3speakcontent.online/tvxkobat/post.png"></a>' ); }); }); diff --git a/src/app/utils/SanitizeConfig.js b/src/app/utils/SanitizeConfig.js index 48afa7d88316bd3e2c816c98d466787debf11c55..3d0bc7c54c6f7c910e9573673b5cf5ea3e3233f0 100644 --- a/src/app/utils/SanitizeConfig.js +++ b/src/app/utils/SanitizeConfig.js @@ -8,61 +8,8 @@ import { getExternalLinkWarningMessage, } from 'shared/HtmlReady'; // the only allowable title attributes for div and a tags -const iframeWhitelist = [ - { - re: /^(https?:)?\/\/player.vimeo.com\/video\/.*/i, - fn: src => { - // <iframe src="https://player.vimeo.com/video/179213493" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> - if (!src) return null; - const m = src.match( - /https:\/\/player\.vimeo\.com\/video\/([0-9]+)/ - ); - if (!m || m.length !== 2) return null; - return 'https://player.vimeo.com/video/' + m[1]; - }, - }, - { - re: /^(https?:)?\/\/www.youtube.com\/embed\/.*/i, - fn: src => { - return src.replace(/\?.+$/, ''); // strip query string (yt: autoplay=1,controls=0,showinfo=0, etc) - }, - }, - { - re: /^(https?:)?\/\/3speak.online\/embed\?v=.*/i, - fn: src => { - return src; - }, - }, - { - re: /^https:\/\/w.soundcloud.com\/player\/.*/i, - fn: src => { - if (!src) return null; - // <iframe width="100%" height="450" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/257659076&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true"></iframe> - const m = src.match(/url=(.+?)&/); - if (!m || m.length !== 2) return null; - return ( - 'https://w.soundcloud.com/player/?url=' + - m[1] + - '&auto_play=false&hide_related=false&show_comments=true' + - '&show_user=true&show_reposts=false&visual=true' - ); - }, - }, - { - re: /^(https?:)?\/\/player.twitch.tv\/.*/i, - fn: src => { - //<iframe src="https://player.twitch.tv/?channel=ninja" frameborder="0" allowfullscreen="true" scrolling="no" height="378" width="620"> - return src; - }, - }, - { - re: /^https:\/\/emb.d.tube\/\#\!\/([a-zA-Z0-9\-\.\/]+)$/, - fn: src => { - // <iframe width="560" height="315" src="https://emb.d.tube/#!/justineh/u6qoydvy" frameborder="0" allowfullscreen></iframe> - return src; - }, - }, -]; +import { validateIframeUrl as validateEmbbeddedPlayerIframeUrl } from 'app/components/elements/EmbeddedPlayers'; + export const noImageText = '(Image not shown due to low ratings)'; export const allowedTags = ` div, iframe, del, @@ -111,31 +58,29 @@ export default ({ transformTags: { iframe: (tagName, attribs) => { const srcAtty = attribs.src; - for (const item of iframeWhitelist) - if (item.re.test(srcAtty)) { - const src = - typeof item.fn === 'function' - ? item.fn(srcAtty, item.re) - : srcAtty; - if (!src) break; - return { - tagName: 'iframe', - attribs: { - frameborder: '0', - allowfullscreen: 'allowfullscreen', - webkitallowfullscreen: 'webkitallowfullscreen', // deprecated but required for vimeo : https://vimeo.com/forums/help/topic:278181 - mozallowfullscreen: 'mozallowfullscreen', // deprecated but required for vimeo - src, - width: large ? '640' : '480', - height: large ? '360' : '270', - }, - }; - } + const validUrl = validateEmbbeddedPlayerIframeUrl(srcAtty); + + if (validUrl !== false) { + return { + tagName: 'iframe', + attribs: { + frameborder: '0', + allowfullscreen: 'allowfullscreen', + webkitallowfullscreen: 'webkitallowfullscreen', // deprecated but required for vimeo : https://vimeo.com/forums/help/topic:278181 + mozallowfullscreen: 'mozallowfullscreen', // deprecated but required for vimeo + src: validUrl, + width: large ? '640' : '480', + height: large ? '360' : '270', + }, + }; + } + console.log( 'Blocked, did not match iframe "src" white list urls:', tagName, attribs ); + sanitizeErrors.push('Invalid iframe URL: ' + srcAtty); return { tagName: 'div', text: `(Unsupported ${srcAtty})` }; }, diff --git a/src/app/utils/SlateEditor/Iframe.js b/src/app/utils/SlateEditor/Iframe.jsx similarity index 66% rename from src/app/utils/SlateEditor/Iframe.js rename to src/app/utils/SlateEditor/Iframe.jsx index fd2bc705557fd31a39710c0992e2bbcc07744751..147f01c688ce5239222b8cdcc71cad8e718e279d 100644 --- a/src/app/utils/SlateEditor/Iframe.js +++ b/src/app/utils/SlateEditor/Iframe.jsx @@ -1,47 +1,11 @@ import React from 'react'; -import linksRe from 'app/utils/Links'; +import { normalizeEmbedUrl as normalizeEmbbeddedPlayerEmbedUrl } from 'app/components/elements/EmbeddedPlayers'; export default class Iframe extends React.Component { normalizeEmbedUrl = url => { - let match; - - // Detect youtube URLs - match = url.match(linksRe.youTubeId); - if (match && match.length >= 2) { - return 'https://www.youtube.com/embed/' + match[1]; - } - - // Detect vimeo - match = url.match(linksRe.vimeoId); - if (match && match.length >= 2) { - return 'https://player.vimeo.com/video/' + match[1]; - } - - // Detect twitch stream - match = url.match(linksRe.twitch); - if (match && match.length >= 3) { - if (match[1] === undefined) { - return ( - 'https://player.twitch.tv/?autoplay=false&channel=' + - match[2] - ); - } else { - return ( - 'https://player.twitch.tv/?autoplay=false&video=' + match[1] - ); - } - } - - // Detect dtube - match = url.match(linksRe.dtubeId); - if (match && match.length >= 2) { - return 'https://emb.d.tube/#!/' + match[1]; - } - - // Detect 3Speak - match = url.match(linksRe.threespeak); - if (match && match.length >= 2) { - return 'https://3speak.online/embed?v=' + match[1]; + const validEmbedUrl = normalizeEmbbeddedPlayerEmbedUrl(url); + if (validEmbedUrl !== false) { + return validEmbedUrl; } console.log('unable to auto-detect embed url', url); @@ -49,7 +13,7 @@ export default class Iframe extends React.Component { }; onChange = e => { - const { node, state, editor } = this.props; + const { node, editor } = this.props; const value = e.target.value; const src = this.normalizeEmbedUrl(value) || value; diff --git a/src/app/utils/UserTemplates.js b/src/app/utils/UserTemplates.js new file mode 100644 index 0000000000000000000000000000000000000000..99e99218f3bb37559f88208dc05c0edbec737a63 --- /dev/null +++ b/src/app/utils/UserTemplates.js @@ -0,0 +1,16 @@ +export const loadUserTemplates = username => { + const lsEntryName = `hivePostTemplates-${username}`; + let userTemplates = window.localStorage.getItem(lsEntryName); + if (userTemplates) { + userTemplates = JSON.parse(userTemplates); + } else { + userTemplates = []; + } + + return userTemplates; +}; + +export const saveUserTemplates = (username, templates) => { + const lsEntryName = `hivePostTemplates-${username}`; + window.localStorage.setItem(lsEntryName, JSON.stringify(templates)); +}; diff --git a/src/server/index.js b/src/server/index.js index 3ccf0259731e1b79d41491acc01526e88e921b26..41cd23467a351839e1566f7881f1a76b7e234118 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -30,6 +30,8 @@ global.$STM_Config = { site_domain: config.get('site_domain'), google_analytics_id: config.get('google_analytics_id'), wallet_url: config.get('wallet_url'), + failover_threshold: config.get('failover_threshold'), + alternative_api_endpoints: config.get('alternative_api_endpoints'), }; const WebpackIsomorphicTools = require('webpack-isomorphic-tools'); @@ -50,9 +52,16 @@ global.webpackIsomorphicTools.server(ROOT, () => { randomize: true, }, useAppbaseApi: !!config.steemd_use_appbase, + alternative_api_endpoints: config.get('alternative_api_endpoints'), + failover_threshold: config.get('failover_threshold'), }); steem.config.set('address_prefix', config.get('address_prefix')); steem.config.set('chain_id', config.get('chain_id')); + steem.config.set( + 'alternative_api_endpoints', + config.get('alternative_api_endpoints') + ); + steem.config.set('failover_threshold', config.get('failover_threshold')); // const CliWalletClient = require('shared/api_client/CliWalletClient').default; // if (process.env.NODE_ENV === 'production') connect_promises.push(CliWalletClient.instance().connect_promise()); diff --git a/src/shared/HtmlReady.js b/src/shared/HtmlReady.js index 1436f654df601aec83f9a71be5748916bd80a070..b735e0795c011f0bed6b11a4e33bd6634bffdfc2 100644 --- a/src/shared/HtmlReady.js +++ b/src/shared/HtmlReady.js @@ -4,6 +4,11 @@ import linksRe, { any as linksAny } from 'app/utils/Links'; import { validate_account_name } from 'app/utils/ChainValidation'; import { proxifyImageUrl } from 'app/utils/ProxifyUrl'; import * as Phishing from 'app/utils/Phishing'; +import { + embedNode as EmbeddedPlayerEmbedNode, + preprocessHtml, +} from 'app/components/elements/EmbeddedPlayers'; +import { extractContentId as youTubeId } from 'app/components/elements/EmbeddedPlayers/youtube'; export const getPhishingWarningMessage = () => tt('g.phishy_message'); export const getExternalLinkWarningMessage = () => @@ -124,13 +129,6 @@ export default function(html, { mutate = true, hideImages = false } = {}) { } } -function preprocessHtml(html) { - // Replacing 3Speak Image/Anchor tag with an embedded player - html = embedThreeSpeakNode(html); - - return html; -} - function traverse(node, state, depth = 0) { if (!node || !node.childNodes) return; Array(...node.childNodes).forEach(child => { @@ -181,6 +179,8 @@ function link(state, child) { // wrap iframes in div.videoWrapper to control size/aspect ratio function iframe(state, child) { const url = child.getAttribute('src'); + + // @TODO move this into the centralized EmbeddedPlayer code if (url) { const { images, links } = state; const yt = youTubeId(url); @@ -245,11 +245,8 @@ function linkifyNode(child, state) { const { mutate } = state; if (!child.data) return; - child = embedYouTubeNode(child, state.links, state.images); - child = embedVimeoNode(child, state.links, state.images); - child = embedTwitchNode(child, state.links, state.images); - child = embedDTubeNode(child, state.links, state.images); - child = embedThreeSpeakNode(child, state.links, state.images); + + child = EmbeddedPlayerEmbedNode(child, state.links, state.images); const data = XMLSerializer.serializeToString(child); const content = linkify( @@ -325,206 +322,6 @@ function linkify(content, mutate, hashtags, usertags, images, links) { return content; } -function embedYouTubeNode(child, links, images) { - try { - const data = child.data; - const yt = youTubeId(data); - if (!yt) return child; - - if (yt.startTime) { - child.data = data.replace( - yt.url, - `~~~ embed:${yt.id} youtube ${yt.startTime} ~~~` - ); - } else { - child.data = data.replace(yt.url, `~~~ embed:${yt.id} youtube ~~~`); - } - - if (links) links.add(yt.url); - if (images) images.add(yt.thumbnail); - } catch (error) { - console.error('yt_node', error); - } - return child; -} - -/** @return {id, url} or <b>null</b> */ -function youTubeId(data) { - if (!data) return null; - - const m1 = data.match(linksRe.youTube); - const url = m1 ? m1[0] : null; - if (!url) return null; - - const m2 = url.match(linksRe.youTubeId); - const id = m2 && m2.length >= 2 ? m2[1] : null; - if (!id) return null; - - const startTime = url.match(/t=(\d+)s?/); - - return { - id, - url, - startTime: startTime ? startTime[1] : 0, - thumbnail: 'https://img.youtube.com/vi/' + id + '/0.jpg', - }; -} - -/** @return {id, url} or <b>null</b> */ -function getThreeSpeakId(data) { - if (!data) return null; - - const match = data.match(linksRe.threespeak); - const url = match ? match[0] : null; - if (!url) return null; - const fullId = match[1]; - const id = fullId.split('/').pop(); - - return { - id, - fullId, - url, - thumbnail: `https://img.3speakcontent.online/${id}/post.png`, - }; -} - -function embedThreeSpeakNode(child, links, images) { - try { - if (typeof child === 'string') { - // If typeof child is a string, this means we are trying to process the HTML - // to replace the image/anchor tag created by 3Speak dApp - const threespeakId = getThreeSpeakId(child); - if (threespeakId) { - child = child.replace( - linksRe.threespeakImageLink, - `~~~ embed:${threespeakId.fullId} threespeak ~~~` - ); - } - } else { - // If child is not a string, we are processing plain text - // to replace a bare URL - const data = child.data; - const threespeakId = getThreeSpeakId(data); - if (!threespeakId) return child; - - child.data = data.replace( - threespeakId.url, - `~~~ embed:${threespeakId.fullId} threespeak ~~~` - ); - - if (links) links.add(threespeakId.url); - if (images) images.add(threespeakId.thumbnail); - } - } catch (error) { - console.log(error); - } - - return child; -} - -function embedVimeoNode(child, links /*images*/) { - try { - const data = child.data; - const vimeo = vimeoId(data); - if (!vimeo) return child; - - const vimeoRegex = new RegExp(`${vimeo.url}(#t=${vimeo.startTime}s?)?`); - if (vimeo.startTime > 0) { - child.data = data.replace( - vimeoRegex, - `~~~ embed:${vimeo.id} vimeo ${vimeo.startTime} ~~~` - ); - } else { - child.data = data.replace( - vimeoRegex, - `~~~ embed:${vimeo.id} vimeo ~~~` - ); - } - - if (links) links.add(vimeo.canonical); - // if(images) images.add(vimeo.thumbnail) // not available - } catch (error) { - console.error('vimeo_embed', error); - } - return child; -} - -function vimeoId(data) { - if (!data) return null; - const m = data.match(linksRe.vimeo); - if (!m || m.length < 2) return null; - - const startTime = m.input.match(/t=(\d+)s?/); - - return { - id: m[1], - url: m[0], - startTime: startTime ? startTime[1] : 0, - canonical: `https://player.vimeo.com/video/${m[1]}`, - // thumbnail: requires a callback - http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo - }; -} - -function embedTwitchNode(child, links /*images*/) { - try { - const data = child.data; - const twitch = twitchId(data); - if (!twitch) return child; - - child.data = data.replace( - twitch.url, - `~~~ embed:${twitch.id} twitch ~~~` - ); - - if (links) links.add(twitch.canonical); - } catch (error) { - console.error('twitch_error', error); - } - return child; -} - -function twitchId(data) { - if (!data) return null; - const m = data.match(linksRe.twitch); - if (!m || m.length < 3) return null; - - return { - id: m[1] === `videos` ? `?video=${m[2]}` : `?channel=${m[2]}`, - url: m[0], - canonical: - m[1] === `videos` - ? `https://player.twitch.tv/?video=${m[2]}` - : `https://player.twitch.tv/?channel=${m[2]}`, - }; -} - -function embedDTubeNode(child, links /*images*/) { - try { - const data = child.data; - const dtube = dtubeId(data); - if (!dtube) return child; - - child.data = data.replace(dtube.url, `~~~ embed:${dtube.id} dtube ~~~`); - - if (links) links.add(dtube.canonical); - } catch (error) { - console.error('dtube_embed', error); - } - return child; -} - -function dtubeId(data) { - if (!data) return null; - const m = data.match(linksRe.dtube); - if (!m || m.length < 2) return null; - - return { - id: m[1], - url: m[0], - canonical: `https://emb.d.tube/#!/${m[1]}`, - }; -} - function ipfsPrefix(url) { if ($STM_Config.ipfs_prefix) { // Convert //ipfs/xxx or /ipfs/xxx into https://steemit.com/ipfs/xxxxx diff --git a/src/shared/HtmlReady.test.js b/src/shared/HtmlReady.test.js index 71da0dc140fcbf8aa0d32d354b8c35f8007e5b29..95722e0b44817bb734c15a0ad37bb06905c8e2c3 100644 --- a/src/shared/HtmlReady.test.js +++ b/src/shared/HtmlReady.test.js @@ -210,7 +210,7 @@ describe('htmlready', () => { expect(res).toEqual(htmlified); }); - it('should handle short youtube link start time', () => { + it('should handle short youtu.be link start time', () => { const testString = '<html><p>https://youtu.be/ToQfmnj7FR8?t=4572s</p></html>'; const htmlified = diff --git a/yarn.lock b/yarn.lock index 0e62b0c19ea97483af2d2e7026ceaf4ffae1da07..3807eaef7a4ec713d36f06014a60834c2919d897 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,10 +15,10 @@ resolved "https://registry.yarnpkg.com/@hivechain/hivescript/-/hivescript-1.1.7.tgz#24d89ef8b0984c6e496934b626a72a374618338a" integrity sha512-hS/negE2DoPqPYzY14+WLqNrdV+4R9pt3AhKkabW6m8auNXOp6i48+ckTMRWT5/lU4/avhjKgOgdqvTAMbXRAg== -"@hiveio/hive-js@0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@hiveio/hive-js/-/hive-js-0.0.1.tgz#2acdce5550adc3f2cc23cb3aad17539360770549" - integrity sha512-aFX58gzLzZvZnPJdEafGRkVYNwQY6Lfqqr5yjr/yFNRXahPd2+m/f/n0e84R894DXB1HcgYASsxR/VwOpNscVg== +"@hiveio/hive-js@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@hiveio/hive-js/-/hive-js-0.0.2.tgz#6f4a6fa133a1528534d410ad5f9bd4fee2b738aa" + integrity sha512-eHNAuYynOTXLTNBpftKEu3raWc2Ot6kj5Fti2OkQdSZaFFryl0hwjI8pH8fTmFnOg7YULOFonYs1Zo+4qhOMeQ== dependencies: "@steemit/rpc-auth" "^1.1.1" bigi "^1.4.2"