diff --git a/.travis.yml b/.travis.yml index c036d4785..6d7db7af1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,17 +2,24 @@ language: node_js node_js: - 12 +services: + - docker + +before_install: + #install heroku CLI + - wget -qO- https://toolbelt.heroku.com/install.sh | sh + script: - - yarn run test + - docker build -t registry.heroku.com/scotbuild/web . + +deploy: + provider: script + script: + bash docker_push + on: + branch: scotty_build2 # safelist branches: only: - - master - - scotty_aaa - - scotty_actnearn - - scotty_bloque64 - - scotty_pal - - scotty_spt - - scotty_steemcoinpan - - scotty_weedcash + - scotty_build2 diff --git a/Dockerfile b/Dockerfile index 16115bea3..68503ac67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.16.2 as development +FROM node:16.9.1 as development WORKDIR /var/app @@ -19,7 +19,7 @@ FROM development as dependencies RUN yarn install --non-interactive --frozen-lockfile --ignore-optional --production ### BUILD MINIFIED PRODUCTION ## -FROM node:12.16.2-alpine as production +FROM node:16.9.1 as production WORKDIR /var/app diff --git a/add_new.sh b/add_new.sh new file mode 100644 index 000000000..2e0eec713 --- /dev/null +++ b/add_new.sh @@ -0,0 +1,122 @@ + +echo $1 # lowercase token +echo $2 # app name + +mkdir -p src/app/assets/static/$1 +cp src/app/assets/static/dunk/manifest.json src/app/assets/static/$1/manifest.json +sed -i -e "s!dunk!$1!" src/app/assets/static/$1/manifest.json +sed -i -e "s!DunkSocial!$2!" src/app/assets/static/$1/manifest.json + +THEMES=$(cat <> src/app/assets/stylesheets/_themes.scss + +echo "Find and place color in src/app/assets/stylesheets/_variables.scss" + + diff --git a/clean_config.sh b/clean_config.sh new file mode 100755 index 000000000..049b136c6 --- /dev/null +++ b/clean_config.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +git checkout HEAD src/app/client_config.js + +git rm -rf src/app/assets/images +git checkout HEAD src/app/assets/images +git rm -rf src/app/assets/static +git checkout HEAD src/app/assets/static +git checkout HEAD src/app/assets/stylesheets diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index ca359b206..6b1ebdf5c 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -17,6 +17,7 @@ } }, "img_proxy_prefix": "SDC_IMAGE_PROXY_PREFIX", + "hive_img_proxy_prefix": "SDC_HIVE_IMAGE_PROXY_PREFIX", "mixpanel": "SDC_MIXPANEL", "notify": { "gcm_key": "SDC_NOTIFY_GCM_KEY" diff --git a/config/default.json b/config/default.json index 6cbf9e938..1f770a9a8 100644 --- a/config/default.json +++ b/config/default.json @@ -5,9 +5,9 @@ "helmet": { "directives": { "childSrc": "'self' 3speak.tv emb.d.tube player.twitch.tv www.youtube.com staticxx.facebook.com w.soundcloud.com player.vimeo.com", - "connectSrc": "wss://ws.beechat.hive-engine.com https://beechat.hive-engine.com https://accounts.hive-engine.com https://history.steem-engine.net https://servedby.revive-adserver.net https://anyx.io https://steemd.minnowsupportproject.org https://cdn.snax.one https://api.hive-engine.com https://ha.herpc.dtools.dev https://api.steem-engine.net https://scot-api.hive-engine.com https://scot-api.steem-engine.net https://steemitimages.com https://images.hive.blog securepubads.g.doubleclick.net 'self' steemit.com https://api.steemit.com https://api.hive.blog api.blocktrades.us https://hivesigner.com https://pagead2.googlesyndication.com http://adservice.google.com https://www.google-analytics.com https://api.openhive.network https://www.reddit.com https://gist.github.com", + "connectSrc": "wss://ws.beechat.hive-engine.com https://api.coingecko.com https://v1.nocodeapi.com https://beechat.hive-engine.com https://accounts.hive-engine.com https://history.steem-engine.net https://servedby.revive-adserver.net https://anyx.io https://steemd.minnowsupportproject.org https://cdn.snax.one https://smtscot.cryptoempirebot.com https://ha.smt-api.dtools.dev https://api.hive-engine.com https://api2.hive-engine.com https://ha.herpc.dtools.dev https://api.steem-engine.net https://hetest.cryptoempirebot.com https://scot-api.hive-engine.com https://scot-api.steem-engine.net https://steemitimages.com https://images.hive.blog securepubads.g.doubleclick.net 'self' steemit.com https://api.steemit.com https://api.hive.blog api.blocktrades.us https://hivesigner.com https://pagead2.googlesyndication.com http://adservice.google.com https://www.google-analytics.com https://api.openhive.network https://www.reddit.com https://gist.github.com", "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 cdn.embedly.com", + "fontSrc": "data: 'self' fonts.gstatic.com cdn.embedly.com", "frameAncestors": "'none'", "frameSrc": "'self' googleads.g.doubleclick.net https:", "imgSrc": "* data:", @@ -21,6 +21,7 @@ "setAllHeaders": true }, "img_proxy_prefix": "https://images.hive.blog/", + "hive_img_proxy_prefix": "https://images.hive.blog/", "ipfs_prefix": false, "mixpanel": false, "notify": { @@ -35,14 +36,15 @@ "session_cookie_key": "hive-dev", "session_key": "haRYmEDmAndOgRempfLetIVI", "site_domain": "hive.blog", - "upload_image": false, - "steemd_connection_client": "https://api.steemit.com", - "steemd_connection_server": "https://api.steemit.com", + "upload_image": "https://images.hive.blog", + "hive_upload_image": "https://images.hive.blog", + "steemd_connection_client": "https://api.hive.blog", + "steemd_connection_server": "https://api.hive.blog", "hive_connection_client": "https://api.hive.blog", "hive_connection_server": "https://api.hive.blog", "steemd_use_appbase": true, "chain_id": "0000000000000000000000000000000000000000000000000000000000000000", - "alternative_api_endpoints": "https://api.openhive.network https://api.hive.blog https://anyx.io", + "alternative_api_endpoints": "https://api.openhive.network https://api.hive.blog", "failover_threshold": 3, "address_prefix": "STM", "conveyor_posting_wif": false, diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 000000000..16e339b54 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,5 @@ +sudo docker build -t registry.heroku.com/scotbuild/web . + +sudo docker push registry.heroku.com/scotbuild/web:latest + +heroku container:release web --app scotbuild diff --git a/docker_push b/docker_push new file mode 100755 index 000000000..80b210841 --- /dev/null +++ b/docker_push @@ -0,0 +1,7 @@ +#!/bin/bash +echo "Docker Login" +echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin registry.heroku.com +echo "Docker Push" +docker push registry.heroku.com/scotbuild/web:latest +echo "Heroku Release" +heroku container:release web --app scotbuild diff --git a/package.json b/package.json index 1a41e892b..2b6b3d66f 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@babel/preset-react": "^7.0.0", "@babel/register": "^7.12.1", "@babel/runtime": "^7.12.5", + "@hive-engine/sscjs": "^0.0.9", "@hiveio/hive-js": "^2.0.4", "@hiveio/hivescript": "^1.2.0", "@livechat/ui-kit": "0.2.13", @@ -145,7 +146,6 @@ "slate-trailing-block": "0.2.4", "speakingurl": "9.0.0", "squarify": "1.1.0", - "@hive-engine/sscjs": "^0.0.9", "statsd-client": "0.4.2", "steem": "0.7.9", "steemscript": "1.1.1", @@ -212,7 +212,7 @@ "koa-webpack-hot-middleware": "1.0.3", "lint-staged": "6.0.0", "mock-local-storage": "1.0.5", - "node-sass": "4.13.1", + "node-sass": "6.x.x", "node-watch": "0.5.5", "picturefill": "3.0.2", "prettier": "1.8.2", @@ -233,7 +233,7 @@ "webpack-livereload-plugin": "1" }, "engines": { - "node": "12.x", + "node": "16.x", "yarn": "1.x" }, "lint-staged": { diff --git a/src/app/Main.js b/src/app/Main.js index 1273e27a1..a5c9c51c1 100644 --- a/src/app/Main.js +++ b/src/app/Main.js @@ -112,7 +112,7 @@ function runApp(initial_state) { }); window.$STM_Config = config; - plugins(config); + plugins(config, initial_state.app.hostConfig); if (initial_state.offchain.serverBusy) { window.$STM_ServerBusy = true; } @@ -153,11 +153,11 @@ if (!window.Intl) { window.IntlPolyfill = window.Intl = require('intl/dist/Intl'); require('intl/locale-data/jsonp/en-US.js'); require('intl/locale-data/jsonp/es.js'); - require('intl/locale-data/jsonp/ru.js'); require('intl/locale-data/jsonp/fr.js'); require('intl/locale-data/jsonp/it.js'); - require('intl/locale-data/jsonp/ko.js'); require('intl/locale-data/jsonp/ja.js'); + require('intl/locale-data/jsonp/ko.js'); + require('intl/locale-data/jsonp/ru.js'); Iso.bootstrap(runApp); }, 'IntlBundle' diff --git a/src/app/Translator.js b/src/app/Translator.js index af292b495..5bb5c3e85 100644 --- a/src/app/Translator.js +++ b/src/app/Translator.js @@ -42,6 +42,9 @@ tt.registerTranslations('pl', require('app/locales/pl.json')); tt.registerTranslations('ja', require('app/locales/counterpart/ja')); tt.registerTranslations('ja', require('app/locales/ja.json')); +tt.registerTranslations('ua', require('app/locales/counterpart/ua')); +tt.registerTranslations('ua', require('app/locales/ua.json')); + if (process.env.NODE_ENV === 'production') { tt.setFallbackLocale('en'); } diff --git a/src/app/assets/ads.txt b/src/app/assets/ads.txt index be24ec6cf..44ebf637c 100644 --- a/src/app/assets/ads.txt +++ b/src/app/assets/ads.txt @@ -1,169 +1,4 @@ -#AppNexus -appnexus.com, 8394, DIRECT -#Rubicon: -rubiconproject.com,18812, DIRECT, 0bfd66d529a55807 -#A9 -aps.amazon.com,747b8b51-ec47-4dee-9823-b2b73124b71f,DIRECT -pubmatic.com,157150,RESELLER,5d62403b186f2ace -openx.com,540191398,RESELLER,6a698e2ec38604c6 -rubiconproject.com,18020,RESELLER,0bfd66d529a55807 -appnexus.com,1908,RESELLER,f5ab79cb980f11d1 -adtech.com,12068,RESELLER,e1a5b5b6e3255540 -ad-generation.jp,12474,RESELLER -districtm.io,100962,RESELLER,3fd707be9c4527c3 -rhythmone.com,1654642120,RESELLER,a670c89d4a324e47 -#Pubmatic -pubmatic.com, 156691, DIRECT, 5d62403b186f2ace -#bRealTime -EMXDGT.com, 375, RESELLER, 1e1d41537f7cad7f -Appnexus.com, 1356, RESELLER, f5ab79cb980f11d1 -#DistrictM -districtm.io, 100983, DIRECT -appnexus.com, 7944, RESELLER -appnexus.com, 1908, RESELLER -#Sekindoa -sekindo.com, 23938, DIRECT -spotxchange.com, 84294, RESELLER, 7842df1d2fe2db34 -spotx.tv, 84294, RESELLER, 7842df1d2fe2db34 -advertising.com, 7372, RESELLER -advertising.com, 24410, RESELLER -inner-active.com, 206396, RESELLER -lkqd.net, 244, RESELLER, 59c49fa9598a0117 -lkqd.com, 244, RESELLER, 59c49fa9598a0117 -smartadserver.com, 2137, RESELLER -tidaltv.com, 32071, RESELLER -pubmatic.com, 156380, RESELLER, 5d62403b186f2ace -appnexus.com, 1940, RESELLER -appnexus.com, 2764, RESELLER -fyber.com, f14d40df2568b6df4eed292a5ad93716, RESELLER -fyber.com, 3238c1661ce2914845a986d98baf7d59, RESELLER -tremorhub.com, mb9eo-oqsbf, RESELLER, 1a4e959a1b50034a -vi.ai, 474289066697842, RESELLER -freewheel.tv, 19129, RESELLER -freewheel.tv, 19133, RESELLER -adform.com, 2078, RESELLER -improvedigital.com, 1065, RESELLER -#TripleLift -triplelift.com, 7376, DIRECT, 6c33edb13117fd86 -triplelift.com, 7186, DIRECT, 6c33edb13117fd86 -triplelift.com, 7328, DIRECT, 6c33edb13117fd86 -triplelift.com, 7187, DIRECT, 6c33edb13117fd86 -appnexus.com, 1314, RESELLER -spotxchange.com, 228454, RESELLER, 7842df1d2fe2db34 -spotx.tv, 228454, RESELLER, 7842df1d2fe2db34 -#Google -google.com, pub-2049948180079264, DIRECT, f08c47fec0942fa0 -google.com, pub-9454946816537646, DIRECT, f08c47fec0942fa0 -google.com, pub-8400268452481648, DIRECT, f08c47fec0942fa0 -#Consumable -adtech.com, 10947, DIRECT -aolcloud.net, 10947, DIRECT -appnexus.com, 1356, DIRECT -appnexus.com, 7556, DIRECT -contextweb.com, 560653, DIRECT, 89ff185a4c4e857c -emxdgt.com, 20, DIRECT, 1e1d41537f7cad7f -google.com, pub-6694481294649483, DIRECT -indexexchange.com, 184914, DIRECT -indexexchange.com, 186248, DIRECT, 50b1c356f2c5c8fc -lijit.com, 248396, DIRECT, fafdf38b16bf6b2b -openx.com, 537150004, DIRECT -pubmatic.com, 156319, DIRECT -sonobi.com, e55fb5d7c2, DIRECT, d1a215d9eb5aee9e -sovrn.com, 248396, DIRECT, fafdf38b16bf6b2b -google.com, pub-5995202563537249, DIRECT, f08c47fec0942fa0 -gumgum.com, 11645, DIRECT, ffdef49475d318a9 -indexexchange.com, 175407, DIRECT -openx.com, 537120960, DIRECT -openx.com, 537143344, DIRECT -openx.com, 538959099, DIRECT -openx.com, 83499, DIRECT -pubmatic.com, 137711, DIRECT -pubmatic.com, 156078, DIRECT, 5d62403b186f2ace -pubmatic.com, 156212, DIRECT -pubmatic.com, 62483, DIRECT -rubiconproject.com, 17632, DIRECT, 0bfd66d529a55807 -rhythmone.com, 1059622079, DIRECT, a670c89d4a324e47 -google.com, pub-9685734445476814, DIRECT, f08c47fec0942fa0 -openx.com, 539699341, DIRECT, 6a698e2ec38604c6 -pubmatic.com, 156458, DIRECT, 5d62403b186f2ace -pubmatic.com, 156138, DIRECT, 5d62403b186f2ace -rubiconproject.com, 18890, DIRECT, 0bfd66d529a55807 -pubmatic.com, 157367, DIRECT -indexexchange.com, 187454, DIRECT -appnexus.com, 1360, DIRECT, f5ab79cb980f11d1 -openx.com, 539924617, DIRECT, 6a698e2ec38604c6 -pubmatic.com, 156700, DIRECT, 5d62403b186f2ace -rubiconproject.com, 17960, DIRECT, 0bfd66d529a55807 -sonobi.com, 6e5cfb5420, DIRECT, d1a215d9eb5aee9e -consumable.com, 2000920, DIRECT -#Sharethrough -sharethrough.com, 81c1429f, DIRECT, d53b998a7bd4ecd2 -spotxchange.com, 212457, RESELLER -spotx.tv, 212457, RESELLER -pubmatic.com, 156557, RESELLER -rubiconproject.com, 18694, RESELLER, 0bfd66d529a55807 -openx.com, 540274407, RESELLER, 6a698e2ec38604c6 -indexexchange.com, 186046, RESELLER -#nobid -nobid.io, 21851990727, DIRECT -google.com, pub-1789253751882305, DIRECT, f08c47fec0942fa0 -google.com, pub-1950215063958302, DIRECT, f08c47fec0942fa0 -gumgum.com, 13926, DIRECT, ffdef49475d318a9 -33across.com, 0010b00002Mq2FYAAZ, DIRECT, bbea06d9c4d2853c -rubiconproject.com, 16414, RESELLER, 0bfd66d529a55807 -pubmatic.com, 156423, RESELLER, 5d62403b186f2ace -appnexus.com, 10239, RESELLER, f5ab79cb980f11d1 -appnexus.com, 1001, RESELLER, f5ab79cb980f11d1 -appnexus.com, 3135, RESELLER, f5ab79cb980f11d1 -openx.com, 537120563, RESELLER, 6a698e2ec38604c6 -openx.com, 539392223, RESELLER, 6a698e2ec38604c6 -rhythmone.com, 2439829435, RESELLER, a670c89d4a324e47 -emxdgt.com, 326, RESELLER, 1e1d41537f7cad7f -gumgum.com, 13318, RESELLER, ffdef49475d318a9 -google.com, pub-9557089510405422, RESELLER, f08c47fec0942fa0 -adtech.com, 9993, RESELLER -adtech.com, 12094, RESELLER -advangelists.com, 8d3bba7425e7c98c50f52ca1b52d3735, RESELLER, 60d26397ec060f98 -#rumble -facebook.com, 358719497878746, RESELLER -google.com, pub-7026201757273977, RESELLER, f08c47fec0942fa0 -google.com, pub-7353644945201577, RESELLER, f08c47fec0942fa0 -rhythmone.com, 752824437, RESELLER, a670c89d4a324e47 -rhythmone.com, 3526250853, RESELLER, a670c89d4a324e47 -chocolateplatform.com, 13062, RESELLER -openx.com, 538808047, RESELLER -openx.com, 538808048, RESELLER -openx.com, 538808049, RESELLER -openx.com, 538808050, RESELLER -openx.com, 540344750, RESELLER, 6a698e2ec38604c6 -indexexchange.com, 184032, RESELLER -indexexchange.com, 187675, RESELLER -vdopia.com, 14057, RESELLER -chocolateplatform.com, 14057, RESELLER -spotxchange.com,252547,RESELLER,7842df1d2fe2db34 -spotx.tv,252547,RESELLER,7842df1d2fe2db34 -#StreamRail -ironsrc.com, 5d99c6e0f1782100011ade7b, DIRECT -ironsrc.com, 5a169d7d9515390002000001, DIRECT -spotx.tv, 139784, RESELLER, 7842df1d2fe2db34 -spotxchange.com, 139784, RESELLER, 7842df1d2fe2db34 -telaria.com, h3vde-gbref, RESELLER, 1a4e959a1b50034a -tremorhub.com, h3vde-gbref, RESELLER, 1a4e959a1b50034a -openx.com, 537140488, RESELLER, 6a698e2ec38604c6 -Advertising.com, 8693, RESELLER -rubiconproject.com, 15526, RESELLER, 0bfd66d529a55807 -freewheel.tv, 790865, RESELLER -freewheel.tv, 790881, RESELLER -appnexus.com, 2480, RESELLER -improvedigital.com, 1114, RESELLER -loopme.com, 5033, RESELLER, 6c8d5f95897a5a3b -loopme.com, s-2411, RESELLER, 6c8d5f95897a5a3b -loopme.com, 10287, RESELLER, 6c8d5f95897a5a3b -springserve.com, 1, RESELLER, a24eb641fc82e93d -contextweb.com, 561620, RESELLER, 89ff185a4c4e857c -beachfront.com, 4021, RESELLER, e2541279e8e2ca4d -rhythmone.com,1352466146,RESELLER,a670c89d4a324e47 -advertising.com, 3679, RESELLER #video -advertising.com, 5831, RESELLER #video -advertising.com, 8044, RESELLER #video -advertising.com, 8022, RESELLER #video \ No newline at end of file +# DBLOG +google.com, pub-8763908884278473, DIRECT, f08c47fec0942fa0 +# FOODIE +google.com, pub-1391439792985803, DIRECT, f08c47fec0942fa0 diff --git a/src/app/assets/brave-rewards-verification.txt b/src/app/assets/brave-rewards-verification.txt new file mode 100644 index 000000000..76420b6bc --- /dev/null +++ b/src/app/assets/brave-rewards-verification.txt @@ -0,0 +1,4 @@ +This is a Brave Rewards publisher verification file. + +Domain: weedcash.network +Token: 954aa2c64211f04710cff30e1af38174c9d5965e69bea93e495cd0f1b3c17c16 diff --git a/src/app/assets/icons/buildcaravan.svg b/src/app/assets/icons/buildcaravan.svg new file mode 100644 index 000000000..fa49e65c0 --- /dev/null +++ b/src/app/assets/icons/buildcaravan.svg @@ -0,0 +1,81 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/src/app/assets/icons/chevron-down-circle.svg b/src/app/assets/icons/chevron-down-circle.svg index 72dad27ef..ad5c10055 100644 --- a/src/app/assets/icons/chevron-down-circle.svg +++ b/src/app/assets/icons/chevron-down-circle.svg @@ -1,7 +1,21 @@ - - - - - - + + + + + diff --git a/src/app/assets/icons/chevron-up-circle-red.svg b/src/app/assets/icons/chevron-up-circle-red.svg new file mode 100644 index 000000000..63dde71b4 --- /dev/null +++ b/src/app/assets/icons/chevron-up-circle-red.svg @@ -0,0 +1,22 @@ + + + + + + diff --git a/src/app/assets/icons/chevron-up-circle.svg b/src/app/assets/icons/chevron-up-circle.svg index c02e4b758..f7bfe7536 100644 --- a/src/app/assets/icons/chevron-up-circle.svg +++ b/src/app/assets/icons/chevron-up-circle.svg @@ -1,8 +1,22 @@ - - - - - - + + + + diff --git a/src/app/assets/icons/weedcash.svg b/src/app/assets/icons/weedcash.svg new file mode 100644 index 000000000..7d0d141b6 --- /dev/null +++ b/src/app/assets/icons/weedcash.svg @@ -0,0 +1,2648 @@ + + + + diff --git a/src/app/assets/images/beatcz.png b/src/app/assets/images/beatcz.png new file mode 100644 index 000000000..452eeccfc Binary files /dev/null and b/src/app/assets/images/beatcz.png differ diff --git a/src/app/assets/images/beatcz.svg b/src/app/assets/images/beatcz.svg new file mode 100644 index 000000000..4ab40fe14 --- /dev/null +++ b/src/app/assets/images/beatcz.svg @@ -0,0 +1,316 @@ + + + + diff --git a/src/app/assets/images/build-it.png b/src/app/assets/images/build-it.png new file mode 100644 index 000000000..06b411bf6 Binary files /dev/null and b/src/app/assets/images/build-it.png differ diff --git a/src/app/assets/images/build-it.svg b/src/app/assets/images/build-it.svg new file mode 100644 index 000000000..8ec6fc821 --- /dev/null +++ b/src/app/assets/images/build-it.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/assets/images/cent.png b/src/app/assets/images/cent.png new file mode 100644 index 000000000..00b85c2f0 Binary files /dev/null and b/src/app/assets/images/cent.png differ diff --git a/src/app/assets/images/cent.svg b/src/app/assets/images/cent.svg new file mode 100644 index 000000000..1ad7922a3 --- /dev/null +++ b/src/app/assets/images/cent.svg @@ -0,0 +1,450 @@ + + + + diff --git a/src/app/assets/images/dunksocial.png b/src/app/assets/images/dunksocial.png new file mode 100644 index 000000000..9daba3999 Binary files /dev/null and b/src/app/assets/images/dunksocial.png differ diff --git a/src/app/assets/images/dunksocial.svg b/src/app/assets/images/dunksocial.svg new file mode 100644 index 000000000..8ba83a903 --- /dev/null +++ b/src/app/assets/images/dunksocial.svg @@ -0,0 +1,9645 @@ + + + + diff --git a/src/app/assets/images/favicon.ico b/src/app/assets/images/favicon.ico deleted file mode 100644 index eba89fa01..000000000 Binary files a/src/app/assets/images/favicon.ico and /dev/null differ diff --git a/src/app/assets/images/favicons/android-chrome-144x144.png b/src/app/assets/images/favicons/android-chrome-144x144.png deleted file mode 100644 index ec20b117e..000000000 Binary files a/src/app/assets/images/favicons/android-chrome-144x144.png and /dev/null differ diff --git a/src/app/assets/images/favicons/android-chrome-192x192.png b/src/app/assets/images/favicons/android-chrome-192x192.png deleted file mode 100644 index fc288a89d..000000000 Binary files a/src/app/assets/images/favicons/android-chrome-192x192.png and /dev/null differ diff --git a/src/app/assets/images/favicons/android-chrome-36x36.png b/src/app/assets/images/favicons/android-chrome-36x36.png deleted file mode 100644 index 9b3b66db0..000000000 Binary files a/src/app/assets/images/favicons/android-chrome-36x36.png and /dev/null differ diff --git a/src/app/assets/images/favicons/android-chrome-48x48.png b/src/app/assets/images/favicons/android-chrome-48x48.png deleted file mode 100644 index ae7a4ead5..000000000 Binary files a/src/app/assets/images/favicons/android-chrome-48x48.png and /dev/null differ diff --git a/src/app/assets/images/favicons/android-chrome-72x72.png b/src/app/assets/images/favicons/android-chrome-72x72.png deleted file mode 100644 index 35cf1fef7..000000000 Binary files a/src/app/assets/images/favicons/android-chrome-72x72.png and /dev/null differ diff --git a/src/app/assets/images/favicons/android-chrome-96x96.png b/src/app/assets/images/favicons/android-chrome-96x96.png deleted file mode 100644 index 7c15f34fb..000000000 Binary files a/src/app/assets/images/favicons/android-chrome-96x96.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-114x114.png b/src/app/assets/images/favicons/apple-touch-icon-114x114.png deleted file mode 100644 index f5400678b..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-114x114.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-120x120.png b/src/app/assets/images/favicons/apple-touch-icon-120x120.png deleted file mode 100644 index a5cc02f05..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-120x120.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-144x144.png b/src/app/assets/images/favicons/apple-touch-icon-144x144.png deleted file mode 100644 index 4a3cd9b65..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-144x144.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-152x152.png b/src/app/assets/images/favicons/apple-touch-icon-152x152.png deleted file mode 100644 index 233c578c4..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-152x152.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-180x180.png b/src/app/assets/images/favicons/apple-touch-icon-180x180.png deleted file mode 100644 index 88a1669a9..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-180x180.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-57x57.png b/src/app/assets/images/favicons/apple-touch-icon-57x57.png deleted file mode 100644 index 10c60f444..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-57x57.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-60x60.png b/src/app/assets/images/favicons/apple-touch-icon-60x60.png deleted file mode 100644 index 8538ef73e..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-60x60.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-72x72.png b/src/app/assets/images/favicons/apple-touch-icon-72x72.png deleted file mode 100644 index 35cf1fef7..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-72x72.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon-76x76.png b/src/app/assets/images/favicons/apple-touch-icon-76x76.png deleted file mode 100644 index ffd765724..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon-76x76.png and /dev/null differ diff --git a/src/app/assets/images/favicons/apple-touch-icon.png b/src/app/assets/images/favicons/apple-touch-icon.png deleted file mode 100644 index 88a1669a9..000000000 Binary files a/src/app/assets/images/favicons/apple-touch-icon.png and /dev/null differ diff --git a/src/app/assets/images/favicons/browserconfig.xml b/src/app/assets/images/favicons/browserconfig.xml deleted file mode 100644 index 628c382ac..000000000 --- a/src/app/assets/images/favicons/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #ffffff - - - diff --git a/src/app/assets/images/favicons/buidl/android-icon-144x144.png b/src/app/assets/images/favicons/buidl/android-icon-144x144.png new file mode 100644 index 000000000..b512c59e1 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/android-icon-144x144.png differ diff --git a/src/app/assets/images/favicons/buidl/android-icon-192x192.png b/src/app/assets/images/favicons/buidl/android-icon-192x192.png new file mode 100644 index 000000000..86f439b12 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/android-icon-192x192.png differ diff --git a/src/app/assets/images/favicons/buidl/android-icon-36x36.png b/src/app/assets/images/favicons/buidl/android-icon-36x36.png new file mode 100644 index 000000000..76b75bc6d Binary files /dev/null and b/src/app/assets/images/favicons/buidl/android-icon-36x36.png differ diff --git a/src/app/assets/images/favicons/buidl/android-icon-48x48.png b/src/app/assets/images/favicons/buidl/android-icon-48x48.png new file mode 100644 index 000000000..e4b9c25ce Binary files /dev/null and b/src/app/assets/images/favicons/buidl/android-icon-48x48.png differ diff --git a/src/app/assets/images/favicons/buidl/android-icon-72x72.png b/src/app/assets/images/favicons/buidl/android-icon-72x72.png new file mode 100644 index 000000000..3088f4a69 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/android-icon-72x72.png differ diff --git a/src/app/assets/images/favicons/buidl/android-icon-96x96.png b/src/app/assets/images/favicons/buidl/android-icon-96x96.png new file mode 100644 index 000000000..3fa7cb149 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/android-icon-96x96.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-114x114.png b/src/app/assets/images/favicons/buidl/apple-icon-114x114.png new file mode 100644 index 000000000..8622f3428 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-114x114.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-120x120.png b/src/app/assets/images/favicons/buidl/apple-icon-120x120.png new file mode 100644 index 000000000..abfc1d999 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-120x120.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-144x144.png b/src/app/assets/images/favicons/buidl/apple-icon-144x144.png new file mode 100644 index 000000000..b512c59e1 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-144x144.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-152x152.png b/src/app/assets/images/favicons/buidl/apple-icon-152x152.png new file mode 100644 index 000000000..1df896109 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-152x152.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-180x180.png b/src/app/assets/images/favicons/buidl/apple-icon-180x180.png new file mode 100644 index 000000000..8176dda57 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-180x180.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-57x57.png b/src/app/assets/images/favicons/buidl/apple-icon-57x57.png new file mode 100644 index 000000000..32b7bf908 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-57x57.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-60x60.png b/src/app/assets/images/favicons/buidl/apple-icon-60x60.png new file mode 100644 index 000000000..7d20035c5 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-60x60.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-72x72.png b/src/app/assets/images/favicons/buidl/apple-icon-72x72.png new file mode 100644 index 000000000..3088f4a69 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-72x72.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-76x76.png b/src/app/assets/images/favicons/buidl/apple-icon-76x76.png new file mode 100644 index 000000000..1f915b2ff Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-76x76.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon-precomposed.png b/src/app/assets/images/favicons/buidl/apple-icon-precomposed.png new file mode 100644 index 000000000..ea2cc1894 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon-precomposed.png differ diff --git a/src/app/assets/images/favicons/buidl/apple-icon.png b/src/app/assets/images/favicons/buidl/apple-icon.png new file mode 100644 index 000000000..ea2cc1894 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/apple-icon.png differ diff --git a/src/app/assets/images/favicons/buidl/browserconfig.xml b/src/app/assets/images/favicons/buidl/browserconfig.xml new file mode 100644 index 000000000..c55414822 --- /dev/null +++ b/src/app/assets/images/favicons/buidl/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/src/app/assets/images/favicons/buidl/favicon-16x16.png b/src/app/assets/images/favicons/buidl/favicon-16x16.png new file mode 100644 index 000000000..85bda9630 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/favicon-16x16.png differ diff --git a/src/app/assets/images/favicons/buidl/favicon-32x32.png b/src/app/assets/images/favicons/buidl/favicon-32x32.png new file mode 100644 index 000000000..2674220ac Binary files /dev/null and b/src/app/assets/images/favicons/buidl/favicon-32x32.png differ diff --git a/src/app/assets/images/favicons/buidl/favicon-96x96.png b/src/app/assets/images/favicons/buidl/favicon-96x96.png new file mode 100644 index 000000000..3fa7cb149 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/favicon-96x96.png differ diff --git a/src/app/assets/images/favicons/buidl/favicon.ico b/src/app/assets/images/favicons/buidl/favicon.ico new file mode 100644 index 000000000..2fd339e4a Binary files /dev/null and b/src/app/assets/images/favicons/buidl/favicon.ico differ diff --git a/src/app/assets/images/favicons/buidl/manifest.json b/src/app/assets/images/favicons/buidl/manifest.json new file mode 100644 index 000000000..b41a3e4ad --- /dev/null +++ b/src/app/assets/images/favicons/buidl/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "/android-icon-36x36.png", + "sizes": "36x36", + "type": "image/png", + "density": "0.75" + }, + { + "src": "/android-icon-48x48.png", + "sizes": "48x48", + "type": "image/png", + "density": "1.0" + }, + { + "src": "/android-icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "density": "1.5" + }, + { + "src": "/android-icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "density": "2.0" + }, + { + "src": "/android-icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "density": "3.0" + }, + { + "src": "/android-icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "density": "4.0" + } + ] +} diff --git a/src/app/assets/images/favicons/buidl/ms-icon-144x144.png b/src/app/assets/images/favicons/buidl/ms-icon-144x144.png new file mode 100644 index 000000000..b512c59e1 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/ms-icon-144x144.png differ diff --git a/src/app/assets/images/favicons/buidl/ms-icon-150x150.png b/src/app/assets/images/favicons/buidl/ms-icon-150x150.png new file mode 100644 index 000000000..1dcd9337c Binary files /dev/null and b/src/app/assets/images/favicons/buidl/ms-icon-150x150.png differ diff --git a/src/app/assets/images/favicons/buidl/ms-icon-310x310.png b/src/app/assets/images/favicons/buidl/ms-icon-310x310.png new file mode 100644 index 000000000..a6b8c2b27 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/ms-icon-310x310.png differ diff --git a/src/app/assets/images/favicons/buidl/ms-icon-70x70.png b/src/app/assets/images/favicons/buidl/ms-icon-70x70.png new file mode 100644 index 000000000..7568ff689 Binary files /dev/null and b/src/app/assets/images/favicons/buidl/ms-icon-70x70.png differ diff --git a/src/app/assets/images/favicons/chrome-web-store-128x128.png b/src/app/assets/images/favicons/chrome-web-store-128x128.png deleted file mode 100644 index 6e1b9d32f..000000000 Binary files a/src/app/assets/images/favicons/chrome-web-store-128x128.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-128.png b/src/app/assets/images/favicons/favicon-128.png deleted file mode 100644 index 36599f894..000000000 Binary files a/src/app/assets/images/favicons/favicon-128.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-144x144.png b/src/app/assets/images/favicons/favicon-144x144.png deleted file mode 100644 index 4a3cd9b65..000000000 Binary files a/src/app/assets/images/favicons/favicon-144x144.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-16x16.png b/src/app/assets/images/favicons/favicon-16x16.png deleted file mode 100644 index 8f00a50e7..000000000 Binary files a/src/app/assets/images/favicons/favicon-16x16.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-196x196.png b/src/app/assets/images/favicons/favicon-196x196.png deleted file mode 100644 index be48eb0f3..000000000 Binary files a/src/app/assets/images/favicons/favicon-196x196.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-24x24.png b/src/app/assets/images/favicons/favicon-24x24.png deleted file mode 100644 index 9e3f1a5c5..000000000 Binary files a/src/app/assets/images/favicons/favicon-24x24.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-32x32.png b/src/app/assets/images/favicons/favicon-32x32.png deleted file mode 100644 index a58d70e37..000000000 Binary files a/src/app/assets/images/favicons/favicon-32x32.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-64x64.png b/src/app/assets/images/favicons/favicon-64x64.png deleted file mode 100644 index eba89fa01..000000000 Binary files a/src/app/assets/images/favicons/favicon-64x64.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-72x72.png b/src/app/assets/images/favicons/favicon-72x72.png deleted file mode 100644 index 35cf1fef7..000000000 Binary files a/src/app/assets/images/favicons/favicon-72x72.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon-96x96.png b/src/app/assets/images/favicons/favicon-96x96.png deleted file mode 100644 index 388539b69..000000000 Binary files a/src/app/assets/images/favicons/favicon-96x96.png and /dev/null differ diff --git a/src/app/assets/images/favicons/favicon.ico b/src/app/assets/images/favicons/favicon.ico deleted file mode 100644 index eba89fa01..000000000 Binary files a/src/app/assets/images/favicons/favicon.ico and /dev/null differ diff --git a/src/app/assets/images/favicons/manifest.json b/src/app/assets/images/favicons/manifest.json deleted file mode 100644 index 65dd72761..000000000 --- a/src/app/assets/images/favicons/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "Golos", - "icons": [ - { - "src": "/images/favicons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/images/favicons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "display": "standalone" -} diff --git a/src/app/assets/images/favicons/mstile-144x144.png b/src/app/assets/images/favicons/mstile-144x144.png deleted file mode 100644 index ec20b117e..000000000 Binary files a/src/app/assets/images/favicons/mstile-144x144.png and /dev/null differ diff --git a/src/app/assets/images/favicons/mstile-150x150.png b/src/app/assets/images/favicons/mstile-150x150.png deleted file mode 100644 index e0b4f0f9b..000000000 Binary files a/src/app/assets/images/favicons/mstile-150x150.png and /dev/null differ diff --git a/src/app/assets/images/favicons/mstile-310x150.png b/src/app/assets/images/favicons/mstile-310x150.png deleted file mode 100644 index cffd4bbcc..000000000 Binary files a/src/app/assets/images/favicons/mstile-310x150.png and /dev/null differ diff --git a/src/app/assets/images/favicons/mstile-310x310.png b/src/app/assets/images/favicons/mstile-310x310.png deleted file mode 100644 index b244e48f1..000000000 Binary files a/src/app/assets/images/favicons/mstile-310x310.png and /dev/null differ diff --git a/src/app/assets/images/favicons/mstile-70x70.png b/src/app/assets/images/favicons/mstile-70x70.png deleted file mode 100644 index 6e1b9d32f..000000000 Binary files a/src/app/assets/images/favicons/mstile-70x70.png and /dev/null differ diff --git a/src/app/assets/images/favicons/opera-speed-dial-195x195.png b/src/app/assets/images/favicons/opera-speed-dial-195x195.png deleted file mode 100644 index 06496912b..000000000 Binary files a/src/app/assets/images/favicons/opera-speed-dial-195x195.png and /dev/null differ diff --git a/src/app/assets/images/steemit.png b/src/app/assets/images/steemit.png deleted file mode 100644 index 1257c7d32..000000000 Binary files a/src/app/assets/images/steemit.png and /dev/null differ diff --git a/src/app/assets/images/steemit.svg b/src/app/assets/images/steemit.svg deleted file mode 100644 index 9e862a7c5..000000000 --- a/src/app/assets/images/steemit.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - STEEMIT/steemit_logo_final Copy 2 - Created with Sketch. - - - - - - - - - - - - - - diff --git a/src/app/assets/images/weedcash.png b/src/app/assets/images/weedcash.png deleted file mode 100644 index c6bf3621f..000000000 Binary files a/src/app/assets/images/weedcash.png and /dev/null differ diff --git a/src/app/assets/images/weedcash.svg b/src/app/assets/images/weedcash.svg deleted file mode 100644 index 1631af456..000000000 --- a/src/app/assets/images/weedcash.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - - diff --git a/src/app/assets/static/buidl/manifest.json b/src/app/assets/static/buidl/manifest.json new file mode 100644 index 000000000..8dfe132a5 --- /dev/null +++ b/src/app/assets/static/buidl/manifest.json @@ -0,0 +1,25 @@ +{ + "name": "BuildIt", + "short_name": "BuildIt", + "theme_color": "#FFFFFF", + "background_color": "#FFFFFF", + "start_url": "/", + "display": "standalone", + "icons": [ + { + "src": "/images/favicons/buidl/favicon-16x16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "/images/favicons/buidl/favicon-32x32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "/images/favicons/buidl/favicon-96x96.png", + "sizes": "96x96", + "type": "image/png" + } + ] +} diff --git a/src/app/assets/static/manifest.json b/src/app/assets/static/manifest.json index 1abe737b4..5436c0cd7 100644 --- a/src/app/assets/static/manifest.json +++ b/src/app/assets/static/manifest.json @@ -7,26 +7,19 @@ "display": "standalone", "icons": [ { - "src": "/images/favicons/favicon-72x72.png", - "sizes": "72x72", + "src": "/images/favicons/favicon-16x16.png", + "sizes": "16x16", "type": "image/png" }, { - "src": "/images/favicons/favicon-96x96.png", - "sizes": "96x96", + "src": "/images/favicons/favicon-32x32.png", + "sizes": "32x32", "type": "image/png" }, { - "src": "/images/favicons/favicon-144x144.png", - "sizes": "144x144", - "type": "image/png" - }, - { - "src": "/images/favicons/favicon-196x196.png", - "sizes": "196x196", + "src": "/images/favicons/favicon-96x96.png", + "sizes": "96x96", "type": "image/png" } - ], - "gcm_sender_id": "724829305784", - "gcm_user_visible_only": true + ] } diff --git a/src/app/assets/stylesheets/_themes.scss b/src/app/assets/stylesheets/_themes.scss index f260dac68..0edc53f1e 100755 --- a/src/app/assets/stylesheets/_themes.scss +++ b/src/app/assets/stylesheets/_themes.scss @@ -1,50 +1,10 @@ $themes: ( - original: ( - colorAccent: $color-blue, - colorAccentHover: $color-blue-original-light, - colorAccentReverse: $color-blue-original-light, - colorWhite: $color-white, - backgroundColor: $color-background-off-white, - backgroundColorEmphasis: $color-background-almost-white, - backgroundColorOpaque: $color-background-off-white, - backgroundTransparent: transparent, - moduleBackgroundColor: $color-white, - menuBackgroundColor: $color-background-dark, - moduleMediumBackgroundColor: $color-white, - navBackgroundColor: $color-white, - highlightBackgroundColor: #f3faf0, - tableRowEvenBackgroundColor: #f4f4f4, - border: 1px solid $color-border-light, - borderLight: 1px solid $color-border-light-lightest, - borderDark: 1px solid $color-text-gray, - borderAccent: 1px solid $color-blue, - borderDotted: 1px dotted $color-border-light, - borderTransparent: transparent, - roundedCorners: 5px, - roundedCornersTop: 5px 5px 0 0, - roundedCornersBottom: 0px 0px 5px 5px, - iconColorSecondary: #cacaca, - textColorPrimary: $color-text-dark, - textColorSecondary: $color-text-gray, - textColorAccent: $color-text-blue, - textColorAccentHover: $color-blue-original-dark, - textColorError: $color-text-red, - contentBorderAccent: $color-transparent, - buttonBackground: $color-blue-original-dark, - buttonBackgroundHover: $color-blue-original-light, - buttonText: $color-text-white, - buttonTextShadow: 0 1px 0 rgba(0,0,0,0.20), - buttonTextHover: $color-text-white, - buttonBoxShadow: $color-transparent, - modalBackgroundColor: $color-background-almost-white, - modalTextColorPrimary: $color-text-dark, - ), - light: ( - colorAccent: $color-teal, - colorAccentHover: $color-teal-dark, + buidl-light: ( + colorAccent: $color-buidl, + colorAccentHover: $color-buidl, colorAccentReverse: $color-blue-black, colorWhite: $color-white, - backgroundColor: $color-background-off-white-dark, + backgroundColor: $color-background-off-white, backgroundColorEmphasis: $color-background-almost-white, backgroundColorOpaque: $color-background-off-white, backgroundTransparent: transparent, @@ -53,76 +13,68 @@ $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, borderDark: 1px solid $color-text-gray, - borderAccent: 1px solid $color-teal, - borderDotted: 1px dotted $color-border-less-light, + borderAccent: 1px solid $color-buidl, + borderDotted: 1px dotted $color-border-light, borderTransparent: transparent, - roundedCorners: 5px, - roundedCornersTop: 5px 5px 0 0, - roundedCornersBottom: 0px 0px 5px 5px, iconColorSecondary: #cacaca, textColorPrimary: $color-text-dark, textColorSecondary: $color-text-gray, - textColorAccent: $color-text-teal, - textColorAccentHover: $color-teal, + textColorAccent: $color-buidl, + textColorAccentHover: $color-buidl, textColorError: $color-text-red, - contentBorderAccent: $color-teal, + contentBorderAccent: $color-buidl, buttonBackground: $color-blue-black, - buttonBackgroundHover: $color-teal, + buttonBackgroundHover: $color-buidl, buttonText: $color-text-white, buttonTextShadow: 0 1px 0 rgba(0,0,0,0.20), buttonTextHover: $color-white, - buttonBoxShadow: $color-teal, + buttonBoxShadow: $color-buidl, buttonBoxShadowHover: $color-blue-black, modalBackgroundColor: $color-white, modalTextColorPrimary: $color-text-dark, ), - dark: ( - colorAccent: $color-teal, - colorAccentHover: $color-teal, + buidl-dark: ( + colorAccent: $color-buidl, + colorAccentHover: $color-buidl, colorAccentReverse: $color-white, colorWhite: $color-white, backgroundColor: $color-background-dark, backgroundColorEmphasis: $color-background-super-dark, backgroundColorOpaque: $color-blue-dark, - moduleBackgroundColor: $color-background-less-dark, + moduleBackgroundColor: $color-background-dark, backgroundTransparent: transparent, menuBackgroundColor: $color-blue-dark, moduleMediumBackgroundColor: $color-background-dark, - navBackgroundColor: $color-background-less-dark, + navBackgroundColor: $color-background-dark, highlightBackgroundColor: $color-blue-black-darkest, - tableRowOddBackgroundColor: #283239, tableRowEvenBackgroundColor: #212C33, - border: 1px solid $color-border-dark-lightest, + border: 1px solid $color-border-dark, borderLight: 1px solid $color-border-dark-lightest, borderDark: 1px solid $color-text-gray-light, - borderAccent: 1px solid $color-teal, + borderAccent: 1px solid $color-buidl, borderDotted: 1px dotted $color-border-dark, borderTransparent: transparent, - roundedCorners: 5px, - roundedCornersTop: 5px 5px 0 0, - roundedCornersBottom: 0px 0px 5px 5px, iconColorSecondary: $color-text-gray-light, textColorPrimary: $color-text-white, textColorSecondary: $color-text-gray-light, - textColorAccent: $color-teal, - textColorAccentHover: $color-teal-light, + textColorAccent: $color-buidl, + textColorAccentHover: $color-buidl, textColorError: $color-text-red, - contentBorderAccent: $color-teal, + contentBorderAccent: $color-buidl, buttonBackground: $color-white, - buttonBackgroundHover: $color-teal, + buttonBackgroundHover: $color-buidl, buttonText: $color-blue-dark, buttonTextShadow: 0 1px 0 rgba(0,0,0,0), buttonTextHover: $color-white, - buttonBoxShadow: $color-teal, + buttonBoxShadow: $color-buidl, buttonBoxShadowHover: $color-white, inputPriceWarning: rgba(255, 153, 0, 0.83), - modalBackgroundColor: $color-background-dark, - modalTextColorPrimary: $color-text-white, + modalBackgroundColor: $color-white, + modalTextColorPrimary: $color-text-dark, ), ); @@ -148,26 +100,6 @@ $themes: ( } -.theme-original { - background-color: $white; - color: $color-text-dark; - @include MQ(M) { - background-color: $color-background-off-white; - } -} -.theme-light { - background-color: $color-background-off-white; - color: $color-text-dark; - @include MQ(M) { - background-color: $color-background-off-white; - } -} -.theme-dark { - background-color: $color-background-dark; - color: $color-text-white; -} - - // Utility classes to be used with @extend .link { @@ -292,17 +224,17 @@ $themes: ( .e-btn { &--black { background-color: $color-blue-black; - box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 5px 5px 0 0 $color-teal; + box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 5px 5px 0 0 $color-black; color: $color-white; &:hover, &:focus { - background-color: $color-teal; + background-color: $color-black; box-shadow: 2px 2px 2px 0 rgba(0,0,0,0.1), 7px 7px 0 0 $color-blue-black; color: $color-white; text-shadow: 0 1px 0 rgba(0,0,0,0.20); } &:visited, &:active { background-color: $color-blue-black; - box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 5px 5px 0 0 $color-teal; + box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 5px 5px 0 0 $color-black; color: $color-white; } &.disabled, &[disabled] { @@ -339,14 +271,22 @@ $themes: ( } } +.theme-buidl-light { + background-color: $white; + color: $color-text-dark; + @include MQ(M) { + background-color: $color-background-off-white; + } +} -.theme-dark, -.theme-light { +.theme-buidl-dark { + background-color: $color-background-dark; + color: $color-text-white; .button.hollow { &:hover, &:focus { - border-color: $color-teal; - color: $color-teal; - outline-color: $color-teal; + border-color: $color-buidl; + color: $color-buidl; + outline-color: $color-buidl; } } } diff --git a/src/app/assets/stylesheets/_variables.scss b/src/app/assets/stylesheets/_variables.scss index 7e9bf1ed4..e096a1895 100755 --- a/src/app/assets/stylesheets/_variables.scss +++ b/src/app/assets/stylesheets/_variables.scss @@ -1,4 +1,4 @@ - +$color-buidl: #4FA5FC; $color-white: #fff; $color-black: #000; diff --git a/src/app/assets/stylesheets/forms.scss b/src/app/assets/stylesheets/forms.scss index e2d70034b..ed54a7aa8 100644 --- a/src/app/assets/stylesheets/forms.scss +++ b/src/app/assets/stylesheets/forms.scss @@ -22,6 +22,7 @@ input, textarea, select { // Overwrite 16px margin-bottom, it was pushing error messages down away from the form element margin-bottom: 0px !important; } + .error { color: $alert-color; margin-bottom: 10px; @@ -70,9 +71,9 @@ p.error { label { @include themify($themes) { - color: themed('textColorPrimary'); - } + color: themed('textColorPrimary'); + } } $input-font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; -$input-font-size: 16px; \ No newline at end of file +$input-font-size: 16px; diff --git a/src/app/assets/stylesheets/foundation-overrides.scss b/src/app/assets/stylesheets/foundation-overrides.scss index 8a400c749..61ffbdea0 100644 --- a/src/app/assets/stylesheets/foundation-overrides.scss +++ b/src/app/assets/stylesheets/foundation-overrides.scss @@ -17,7 +17,9 @@ .VerticalMenu { a:hover { background-color: #f6f6f6; - color: $color-teal-dark; + @include themify($themes) { + color: themed('textColorAccent'); + } } } } @@ -115,14 +117,14 @@ tbody tr:nth-child(even) { } .reveal { - box-shadow: 2px 2px 2px 0 rgba(0,0,0,0.1), 7px 7px 0 0 $color-teal; - border-radius: 0 30px; + border-radius: 0; border: transparent; transition: 0.2s all ease-in-out; outline: none; @include themify($themes) { background-color: themed('modalBackgroundColor'); color: themed('modalTextColorPrimary'); + box-shadow: 0px 0px 0px 0 #30414A, 4px 4px 0 0 themed('colorAccent'); } label { @include themify($themes) { @@ -146,7 +148,9 @@ tbody tr:nth-child(even) { border-radius: 3px; background-color:$color-border-light; div { - background-color: $color-teal; + @include themify($themes) { + background-color: themed('colorAccent'); + } border-radius: 3px; border: none; height: 8px; diff --git a/src/app/client_config.js b/src/app/client_config.js index 76168fb2b..152a7c070 100644 --- a/src/app/client_config.js +++ b/src/app/client_config.js @@ -1,43 +1,58 @@ import { fromJSOrdered } from './utils/immutable'; -// sometimes it's impossible to use html tags to style coin name, hence usage of _UPPERCASE modifier -export const APP_NAME = 'WeedCash'; -// sometimes APP_NAME is written in non-latin characters, but they are needed for technical purposes -// ie. "Голос" > "Golos" -export const APP_NAME_LATIN = 'WeedCash'; -export const APP_NAME_UPPERCASE = 'WEEDCASH'; -export const APP_ICON = 'weedcash'; +const BUIDL = { + APP_NAME: 'BuildIt', + APP_ICON: 'build-it', + APP_ICON_WIDTH: '150px', + APP_ICON_HEIGHT: '40px', + APP_URL: 'https://www.buildl-it.com', + APP_DOMAIN: 'www.buildl-it.com', + LIQUID_TOKEN: 'BUIDL', + LIQUID_TOKEN_UPPERCASE: 'BUIDL', + APP_MAX_TAG: 10, + SCOT_TAG: 'build-it', + TAG_LIST: fromJSOrdered(['build-it', 'buidl', 'diy']), + INTERLEAVE_PROMOTED: true, + PROMOTED_POST_ACCOUNT: 'null', + VESTING_TOKEN: 'BUIDL POWER', + SITE_DESCRIPTION: + 'BuildIt is a social media platform where everyone gets paid for ' + + 'creating and curating content. It leverages a robust digital points system, called BUIDL, that ' + + 'supports real value for digital rewards through market price discovery and liquidity', + // Revive Ads + NO_ADS_STAKE_THRESHOLD: 9999999999, + REVIVE_ADS: {}, + ALLOW_MASTER_PW: false, + // Footer to attach to posts. ${POST_URL} is a macro that can be used, will be expanded to the URL of the post. + POST_FOOTER: '', + COMMENT_FOOTER: '', + SCOT_TAG_FIRST: false, + SDC_GTAG_MEASUREMENT_ID: 'UA-145448693-22', + DISABLE_STEEM: true, + PREFER_HIVE: true, + HIVE_ENGINE: true, + HIVE_ENGINE_SMT: 5, + APPEND_TRENDING_TAGS_COUNT: 10, + COMMUNITY_CATEGORY: 'hive-129017', + CHAT_CONVERSATIONS: [ + { id: '01EPB6A2PPSW0BQVJ7WDDP568C', name: 'BeeChat Trollbox' }, + ], + POSTED_VIA_NITROUS_ICON: 'buildcaravan', + SHOW_AUTHOR_RECENT_POSTS: true, + SHOW_TOKEN_STATS: true, +}; -// FIXME figure out best way to do this on both client and server from env -// vars. client should read $STM_Config, server should read config package. -export const APP_URL = 'https://www.weedcash.network'; -export const APP_DOMAIN = 'www.weedcash.network'; -// max num of tags. if unset, default is 10. This is due to previous hardcoded number. -export const APP_MAX_TAG = 10; -export const SCOT_TAG = 'weedcash'; -export const TAG_LIST = fromJSOrdered([ - 'weedcash', - 'weed', - 'cannabis', - 'psychedelic', -]); -export const LIQUID_TOKEN = 'Weed'; -// sometimes it's impossible to use html tags to style coin name, hence usage of _UPPERCASE modifier -export const LIQUID_TOKEN_UPPERCASE = 'FOODIE'; -// used as backup -export const SCOT_DENOM = 100000000; -export const VOTE_WEIGHT_DROPDOWN_THRESHOLD = 1; -export const VESTING_TOKEN = 'WEED POWER'; -export const INTERLEAVE_PROMOTED = true; +export const CONFIG_MAP = { + // testing heroku/local options + 'localhost:8080': BUIDL, + 'www.buildl-it.com': BUIDL, + 'buildl-it.com': BUIDL, +}; export const HIVE_SIGNER_APP = 'ewd'; export const CURRENCY_SIGN = '$'; export const WIKI_URL = ''; // https://wiki.golos.io/ -export const LANDING_PAGE_URL = 'https://hive.io'; -export const TERMS_OF_SERVICE_URL = 'https://' + APP_DOMAIN + '/tos.html'; -export const PRIVACY_POLICY_URL = 'https://' + APP_DOMAIN + '/privacy.html'; -export const WHITEPAPER_URL = 'https://hive.io/hive-whitepaper.pdf'; // these are dealing with asset types, not displaying to client, rather sending data over websocket export const LIQUID_TICKER = 'HIVE'; @@ -48,81 +63,4 @@ export const DEFAULT_LANGUAGE = 'en'; // used on application internationalizatio export const DEFAULT_CURRENCY = 'USD'; export const ALLOWED_CURRENCIES = ['USD']; -// meta info -export const TWITTER_HANDLE = '@'; -export const SHARE_IMAGE = - 'https://' + APP_DOMAIN + '/images/hive-blog-share.png'; -export const TWITTER_SHARE_IMAGE = - 'https://' + APP_DOMAIN + '/images/hive-blog-twshare.png'; -export const SITE_DESCRIPTION = - 'Weedcash is a social media platform where everyone gets paid for ' + - 'creating and curating content. It leverages a robust digital points system, called WEED, that ' + - 'supports real value for digital rewards through market price discovery and liquidity'; - -// various -export const SUPPORT_EMAIL = 'support@' + APP_DOMAIN; - -// Revive Ads -export const NO_ADS_STAKE_THRESHOLD = 2000; -export const REVIVE_ADS = { - //header_banner: { - // zoneId: '1699', - // reviveId: '727bec5e09208690b050ccfc6a45d384', - //}, - //sidebar_left: { - // zoneId: '1767', - // reviveId: '727bec5e09208690b050ccfc6a45d384', - //}, - //sidebar_right: { - // zoneId: '1761', - // reviveId: '727bec5e09208690b050ccfc6a45d384', - //}, - //post_footer_abovecomments: { - // zoneId: '1768', - // reviveId: '727bec5e09208690b050ccfc6a45d384', - //}, - //post_footer_betweencomments: { - // zoneId: '1769', - // reviveId: '727bec5e09208690b050ccfc6a45d384', - //}, - //feed: { - // zoneId: '1777', - // reviveId: '727bec5e09208690b050ccfc6a45d384', - //}, - //feed_small: { - // zoneId: '', - // reviveId: '727bec5e09208690b050ccfc6a45d384', - //}, -}; - -// Other configurations -export const ALLOW_MASTER_PW = false; -// Footer to attach to posts. ${POST_URL} is a macro that can be used, will be expanded to the URL of the post. -export const POST_FOOTER = ''; -// Footer to attach to commments. ${POST_URL} is a macro that can be used, will be expanded to the URL of the commment. -export const COMMENT_FOOTER = ''; -export const SCOT_TAG_FIRST = false; -export const SCOT_DEFAULT_BENEFICIARY_ACCOUNT = ''; -export const SCOT_DEFAULT_BENEFICIARY_PERCENT = 0; // between 0 amd 100 -export const SHOW_AUTHOR_RECENT_POSTS = false; -export const POSTED_VIA_NITROUS_ICON = ''; // put corresponding svg in src/app/assets/icons/___.svg -export const COMMUNITY_CATEGORY = ''; -export const SHOW_TOKEN_STATS = true; export const TOKEN_STATS_EXCLUDE_ACCOUNTS = []; -export const PREFER_HIVE = true; -export const DISABLE_HIVE = false; -export const HIVE_ENGINE = true; -export const DISABLE_BLACKLIST = false; -export const CHAT_CONVERSATIONS = [ - { id: '01EPB6A2PPSW0BQVJ7WDDP568C', name: 'BeeChat Trollbox' }, -]; -export const APPEND_TRENDING_TAGS_COUNT = 0; -export const TRENDING_TAGS_TO_IGNORE = []; - -export const INVEST_TOKEN_UPPERCASE = HIVE_ENGINE - ? 'HIVE POWER' - : 'STEEM POWER'; -export const INVEST_TOKEN_SHORT = HIVE_ENGINE ? 'HP' : 'SP'; -export const DEBT_TOKEN = HIVE_ENGINE ? 'HIVE DOLLAR' : 'STEEM DOLLAR'; -export const DEBT_TOKENS = HIVE_ENGINE ? 'HIVE DOLLARS' : 'STEEM DOLLARS'; - diff --git a/src/app/components/App.jsx b/src/app/components/App.jsx index 8bb6b94b1..1e8df80c2 100644 --- a/src/app/components/App.jsx +++ b/src/app/components/App.jsx @@ -21,34 +21,73 @@ class App extends React.Component { // TODO: put both of these and associated toggles into Redux Store. this.state = { showCallout: true, - showBanner: false, + showBanner: true, }; this.listenerActive = null; } toggleBodyNightmode(nightmodeEnabled) { + const { scotTokenSymbolLower } = this.props; if (nightmodeEnabled) { - document.body.classList.remove('theme-light'); - document.body.classList.add('theme-dark'); + document.body.classList.remove( + `theme-${scotTokenSymbolLower}-light` + ); + document.body.classList.add(`theme-${scotTokenSymbolLower}-dark`); } else { - document.body.classList.remove('theme-dark'); - document.body.classList.add('theme-light'); + document.body.classList.remove( + `theme-${scotTokenSymbolLower}-dark` + ); + document.body.classList.add(`theme-${scotTokenSymbolLower}-light`); } } - componentWillReceiveProps(nextProps) { - const { nightmodeEnabled } = nextProps; - this.toggleBodyNightmode(nightmodeEnabled); - } + darkMode = e => { + let clickedClass = 'clicked'; + const body = document.body; + const lightTheme = 'theme-buidl-light'; + const darkTheme = 'theme-buidl-dark'; + let theme; + let switchTheme; + + if (localStorage) { + theme = localStorage.getItem('theme'); + } + + switchTheme = e => { + if (theme === darkTheme) { + body.classList.replace(darkTheme, lightTheme); + e.target.classList.remove(clickedClass); + localStorage.setItem('theme', 'theme-buidl-light'); + theme = lightTheme; + } else { + body.classList.replace(lightTheme, darkTheme); + e.target.classList.add(clickedClass); + localStorage.setItem('theme', 'theme-buidl-dark'); + theme = darkTheme; + } + }; + switchTheme(e); + }; componentWillMount() { if (process.env.BROWSER) localStorage.removeItem('autopost'); // July 14 '16 compromise, renamed to autopost2 this.props.loginUser(); } - componentDidMount() { - const { nightmodeEnabled } = this.props; - this.toggleBodyNightmode(nightmodeEnabled); + componentDidMount() { + let theme; + const body = document.body; + const lightTheme = 'theme-buidl-light'; + const darkTheme = 'theme-buidl-dark'; + if (localStorage) { + theme = localStorage.getItem('theme'); + } + + if (theme === lightTheme || theme === darkTheme) { + body.classList.add(theme); + } else { + body.classList.add(lightTheme); + } } shouldComponentUpdate(nextProps, nextState) { @@ -83,6 +122,7 @@ class App extends React.Component { pathname, category, order, + scotTokenSymbolLower, } = this.props; const whistleView = viewMode === VIEW_MODE_WHISTLE; @@ -163,11 +203,9 @@ class App extends React.Component { ); } - const themeClass = nightmodeEnabled ? ' theme-dark' : ' theme-light'; - return (
{process.env.BROWSER && - ip && - new_visitor && - this.state.showBanner ? ( + this.state.showBanner && + pathname === '/' ? ( @@ -219,6 +257,9 @@ export default connect( const current_account_name = current_user ? current_user.get('username') : state.offchain.get('account'); + const scotTokenSymbolLower = state.app + .getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']) + .toLowerCase(); return { viewMode: state.app.get('viewMode'), @@ -237,6 +278,7 @@ export default connect( order: ownProps.params.order, category: ownProps.params.category, showAnnouncement: state.user.get('showAnnouncement'), + scotTokenSymbolLower, }; }, dispatch => ({ diff --git a/src/app/components/App.scss b/src/app/components/App.scss index da0373397..2503f32c6 100644 --- a/src/app/components/App.scss +++ b/src/app/components/App.scss @@ -1,216 +1,309 @@ .App { - min-height: 100vh; - padding-top: 0px; -} + min-height: 100vh; + padding-top: 0px; + } -.App__content { - margin-top: 1rem; - overflow: hidden; -} + .App__content { + margin-top: 1rem; + overflow: hidden; + } -.welcomeWrapper { - padding-bottom: 1rem; -} + .welcomeWrapper { + padding-bottom: 1rem; + } -.RightMenu { - background-color: #555; - height: 100vh; - color: #fff; - padding-top: 3rem; - .close-button { + .RightMenu { + background-color: #555; + height: 100vh; color: #fff; - } - .menu > li { - > a { + padding-top: 3rem; + .close-button { color: #fff; - border-top: 1px solid #777; } - > a:hover { - background-color: #666; + .menu > li { + > a { + color: #fff; + border-top: 1px solid #777; + } + > a:hover { + background-color: #666; + } + } + .menu > li.last { + border-bottom: 1px solid #777; + } + .button.hollow { + color: #fff; + border: none; } } - .menu > li.last { - border-bottom: 1px solid #777; - } - .button.hollow { - color: #fff; - border: none; + + .PlainLink { + @extend .link; + @extend .link--secondary; } -} -.PlainLink { - @extend .link; - @extend .link--secondary; -} + .text-muted { + @include themify($themes) { + color: themed('textColorSecondary'); + svg { + fill: themed('textColorSecondary'); + opacity: 0.7; + } + } + } -.text-muted { - @include themify($themes) { - color: themed('textColorSecondary'); - svg { - fill: themed('textColorSecondary'); - opacity: 0.7; + .mchimp { + margin: auto; + width: 50%; + @media (max-width: 805px) { + margin: auto; + width: 90%; } - } + } + .inputbt { + margin-bottom: 8px !important; + width: 75%; + background-color: #171f24 !important; + color: white !important; + border: 1px solid #30414a !important; } -.welcomeBanner { - margin-top: -1rem; - padding: 0; - background-color: $color-blue-black; - color: $color-white; - position: relative; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05); + .welcomeBanner { + padding: 8.3% 0 0 0; + background-color: $color-blue-black; + color: $color-white; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05); - h2 { - font-weight: bold; - margin-bottom: 16px; - @include font-size(28px); - line-height: 1; - font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; - @include opacity(0); - @include MQ(M) { - @include font-size(34px); - max-width: 280px; - } + img { + max-width: 100vh; + display: inline-block; + vertical-align: middle; + height: auto; } - h4 { - color: $color-white; - font-weight: normal; - margin-bottom: 1rem; - width: 85%; - max-width: 360px; - font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; - line-height: 1.3 !important; - @include font-size(16px); - @include opacity(0); - @include MQ(M) { - @include font-size(17px); - } - } - - .row { - align-items: center; - } - - .button { - min-width: 120px; - white-space: nowrap; - text-decoration: none; - font-weight: bold; - transition: 0.2s all ease-in-out; - text-transform: initial; - border-radius: 0; - background-color: $color-white; - color: $color-blue-black; - border: none; - box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 2px 2px 0 0 $color-teal; - padding: 16px; - margin-right: 18px; - @include font-size(16px); - cursor: pointer; - font-family: $font-primary; - &:hover, &:focus { - background-color: $color-teal; + .welcomeImage-mobile{ + display: none; + } + + h2 { + font-weight: bold; + margin-bottom: 16px; + @include font-size(28px); + line-height: 1; + font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; + @include opacity(0); + @include MQ(M) { + @include font-size(34px); + max-width: 280px; + } + } + + h4 { color: $color-white; - box-shadow: 0px 0px 0px 0 rgba(0,0,0,0.1), 4px 4px 0 0 $color-white; - text-shadow: 0 1px 0 rgba(0,0,0,0.20); + font-weight: normal; + margin-bottom: 2rem; + width: 85%; + max-width: 360px; + font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; + line-height: 1.3 !important; + @include font-size(16px); + @include opacity(0); + @include MQ(M) { + @include font-size(17px); + } } - @include MQ(M) { - @include font-size(18px); - min-width: 132px; + + .row { + align-items: center; + flex: none !important; } - } - .button--primary { - @include opacity(0); - } - .close-button { - top: 0.8rem; - right: 0; - @include MQ(M) { - top: 0.5rem; + .button { + min-width: 120px; + white-space: nowrap; + text-decoration: none; + font-weight: bold; + transition: 0.2s all ease-in-out; + text-transform: initial; + border-radius: 0; + background-color: $color-white; + color: $color-blue-black; + border: none; + @include themify($themes) { + box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 2px 2px 0 0 themed('colorAccent'); + } + padding: 16px; + margin-right: 18px; + @include font-size(16px); + cursor: pointer; + font-family: $font-primary; + &:hover, &:focus { + @include themify($themes) { + background-color: themed('colorAccent'); + } + color: $color-white; + box-shadow: 0px 0px 0px 0 rgba(0,0,0,0.1), 4px 4px 0 0 $color-white; + text-shadow: 0 1px 0 rgba(0,0,0,0.20); + } + @include MQ(M) { + @include font-size(18px); + min-width: 132px; + } } - } - .button.ghost { - background: transparent; - color: $color-white; - border: 1px solid #30414A; - box-shadow: 0px 0px 0px 0 #30414A, 2px 2px 0 0 #30414A; - @include opacity(0); - &:hover, &:focus { - box-shadow: 0px 0px 0px 0 #30414A, 4px 4px 0 0 $color-teal; + .button--primary { + @include opacity(0); + } + .close-button { + top: 0.8rem; + right: 0; + @include MQ(M) { + top: 0.5rem; + } + } + .button.hollow { + background: none; + box-shadow: none; + @include themify($themes) { + color: themed('colorAccent'); + border: 1px solid themed('colorAccent'); + } } - } - .heroImage { - max-height: 360px; - @include opacity(0); - animation: fade-in-up 0.6s ease-out both; - @media screen and (prefers-reduced-motion) { - animation: none; - @include opacity(1); + .button.ghost { + background: transparent; + color: $color-white; + border: 1px solid #30414A; + box-shadow: 0px 0px 0px 0 #30414A, 2px 2px 0 0 #30414A; + @include opacity(0); + &:hover, &:focus { + @include themify($themes) { + box-shadow: 0px 0px 0px 0 #30414A, 4px 4px 0 0 themed('colorAccent'); + } + } + } + .rows{ + position: relative; + width: 90%; + margin: auto; + padding-bottom: 3%; } + .h2txt{ + max-width: 100% !important; + width: 54%; + @media (max-width: 868px) { + width: 94%; + } } + .heroimg { + width: 50%; + position: absolute; + right: 0; + top: -37px; - .welcomeImage { - padding: 1em 0; - @include MQ(L) { - padding: 2em 0; + @include MQ(M) { + top: -14%; + } + @media (max-width: 1199px) { + top: 0%; + } + @media (max-width: 999px) { + top: 6%; + } + @media (max-width: 790px) { + top: 6%; + } } - } - .welcomePitch { - padding: 20px; - @include MQ(M) { - padding: 0; + .heroImage { + @include opacity(0); + animation: fade-in-up 0.6s ease-out both; + border-radius: 5px; + @media screen and (prefers-reduced-motion) { + animation: none; + @include opacity(1); + } } - } -} -.downvoted { - opacity: 0.5; - //-webkit-filter: grayscale(1); // image - //filter: gray; // image grayscale - transition: 0.2s all ease-in-out; - color: #848282; - @include themify($themes) { - color: themed('textColorPrimary'); + .welcomeImage { + padding: 1em 0; + @include MQ(L) { + padding: 2em 0; + } } - .Comment__header-user { + + .welcomePitch { + padding: 10px; + margin-left: 10%; + @include MQ(M) { + padding: 10px; + margin-left: 10%; + } + @media (max-width: 1199px) { + margin-left: 6%; + } + @media (max-width: 999px) { + margin-left: 4%; + } + @media (max-width: 790px) { + margin-left: 2%; + } + @media (max-width: 490px) { + margin-left: 0%; + } + } + } + .Header__search{ + margin-right: 13px; + } + + .flexBtn{ + display: flex; + } + .downvoted { + opacity: 0.5; + //-webkit-filter: grayscale(1); // image + //filter: gray; // image grayscale + transition: 0.2s all ease-in-out; color: #848282; @include themify($themes) { color: themed('textColorPrimary'); } - } -} -.downvoted:hover { - opacity: 1; - filter: none; - -webkit-filter: none; -} + .Comment__header-user { + color: #848282; + @include themify($themes) { + color: themed('textColorPrimary'); + } + } + } + .downvoted:hover { + opacity: 1; + filter: none; + -webkit-filter: none; + } -.App__announcement { - padding-right: 40px; - padding-top: 40px; - .close-button { - right: 0; + .App__announcement { + padding-right: 40px; + padding-top: 40px; + .close-button { + right: 0; + } } -} -.beta-disclaimer { - @include themify($themes) { - padding: 8px 24px; - font-size: 0.8em; - color: themed('textColorPrimary'); - background: repeating-linear-gradient( - 45deg, - themed('backgroundColor'), - themed('backgroundColor') 10px, - themed('moduleBackgroundColor') 10px, - themed('moduleBackgroundColor') 20px - ); - } -} + .beta-disclaimer { + @include themify($themes) { + padding: 8px 24px; + font-size: 0.8em; + color: themed('textColorPrimary'); + background: repeating-linear-gradient( + 45deg, + themed('backgroundColor'), + themed('backgroundColor') 10px, + themed('moduleBackgroundColor') 10px, + themed('moduleBackgroundColor') 20px + ); + } + } diff --git a/src/app/components/DarkMode/DarkModeBtn.jsx b/src/app/components/DarkMode/DarkModeBtn.jsx new file mode 100644 index 000000000..d697085d7 --- /dev/null +++ b/src/app/components/DarkMode/DarkModeBtn.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import moondark from './moon.png'; +import moonlight from './moon_light.png'; +const DarkModeBtn = ({ toggleBody }) => { + return ( +
+ +
+ ); +}; + +export default DarkModeBtn; diff --git a/src/app/components/DarkMode/moon.png b/src/app/components/DarkMode/moon.png new file mode 100644 index 000000000..22116337b Binary files /dev/null and b/src/app/components/DarkMode/moon.png differ diff --git a/src/app/components/DarkMode/moon_light.png b/src/app/components/DarkMode/moon_light.png new file mode 100644 index 000000000..159b913e2 Binary files /dev/null and b/src/app/components/DarkMode/moon_light.png differ diff --git a/src/app/components/cards/BeneficiarySelector.jsx b/src/app/components/cards/BeneficiarySelector.jsx index 55e87ee71..aefd1e0d2 100644 --- a/src/app/components/cards/BeneficiarySelector.jsx +++ b/src/app/components/cards/BeneficiarySelector.jsx @@ -5,10 +5,6 @@ import { validate_account_name } from 'app/utils/ChainValidation'; import reactForm from 'app/utils/ReactForm'; import { List, Set } from 'immutable'; import tt from 'counterpart'; -import { - SCOT_DEFAULT_BENEFICIARY_ACCOUNT, - SCOT_DEFAULT_BENEFICIARY_PERCENT, -} from 'app/client_config'; export class BeneficiarySelector extends React.Component { static propTypes = { @@ -80,11 +76,17 @@ export class BeneficiarySelector extends React.Component { }; render() { - const { username, following, tabIndex } = this.props; + const { + username, + following, + tabIndex, + defaultBeneficiaryAccount, + defaultBeneficiaryPercent, + } = this.props; const beneficiaries = this.props.value; const remainingPercent = 100 - - SCOT_DEFAULT_BENEFICIARY_PERCENT - + defaultBeneficiaryPercent - beneficiaries .map(b => (b.percent ? parseInt(b.percent) : 0)) .reduce((sum, elt) => sum + elt, 0); @@ -119,7 +121,7 @@ export class BeneficiarySelector extends React.Component {
- {SCOT_DEFAULT_BENEFICIARY_ACCOUNT && ( + {defaultBeneficiaryAccount && (
@@ -127,7 +129,7 @@ export class BeneficiarySelector extends React.Component { id="benePercent" type="text" pattern="[0-9]*" - value={SCOT_DEFAULT_BENEFICIARY_PERCENT} + value={defaultBeneficiaryPercent} disabled style={{ maxWidth: '2.6rem', @@ -146,7 +148,7 @@ export class BeneficiarySelector extends React.Component { className="input-group-field bold" type="text" disabled - value={SCOT_DEFAULT_BENEFICIARY_ACCOUNT} + value={defaultBeneficiaryAccount} />
@@ -249,7 +251,8 @@ export class BeneficiarySelector extends React.Component { export function validateBeneficiaries( username, beneficiaries, - required = true + required = true, + defaultBeneficiaryPercent = 0 ) { if (beneficiaries.length > 8) { return tt('beneficiary_selector_jsx.exceeds_max_beneficiaries'); @@ -281,7 +284,7 @@ export function validateBeneficiaries( } totalPercent += parseInt(beneficiary.percent); } - if (totalPercent > 100 - SCOT_DEFAULT_BENEFICIARY_PERCENT) { + if (totalPercent > 100 - defaultBeneficiaryPercent) { return tt('beneficiary_selector_jsx.beneficiary_percent_total_invalid'); } } @@ -292,6 +295,15 @@ export default connect((state, ownProps) => { let following = List(); const username = state.user.getIn(['current', 'username']); const follow = state.global.get('follow'); + const defaultBeneficiaryAccount = state.app.getIn([ + 'hostConfig', + 'SCOT_DEFAULT_BENEFICIARY_ACCOUNT', + ]); + const defaultBeneficiaryPercent = state.app.getIn( + ['hostConfig', 'SCOT_DEFAULT_BENEFICIARY_PERCENT'], + 0 + ); + if (follow) { const followingData = follow.getIn([ 'getFollowingAsync', @@ -304,5 +316,7 @@ export default connect((state, ownProps) => { ...ownProps, username, following: following.toJS(), + defaultBeneficiaryAccount, + defaultBeneficiaryPercent, }; })(BeneficiarySelector); diff --git a/src/app/components/cards/CategorySelector.jsx b/src/app/components/cards/CategorySelector.jsx index 473c15981..a2b483d4d 100644 --- a/src/app/components/cards/CategorySelector.jsx +++ b/src/app/components/cards/CategorySelector.jsx @@ -4,9 +4,6 @@ import { connect } from 'react-redux'; import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; import { cleanReduxInput } from 'app/utils/ReduxForms'; import tt from 'counterpart'; -import { APP_MAX_TAG } from 'app/client_config'; - -const MAX_TAG = APP_MAX_TAG || 10; class CategorySelector extends React.Component { static propTypes = { @@ -23,6 +20,7 @@ class CategorySelector extends React.Component { // redux connect (overwrite in HTML) trending: PropTypes.object.isRequired, // Immutable.List + scotMaxTags: PropTypes.number, }; static defaultProps = { autoComplete: 'on', @@ -56,7 +54,7 @@ class CategorySelector extends React.Component { }; } render() { - const { trending, tabIndex, disabled } = this.props; + const { trending, scotMaxTags, tabIndex, disabled } = this.props; // TODO: figure out category tags const categories = trending ? trending.slice(0, 11).filterNot(c => validateCategory(c)) @@ -99,15 +97,15 @@ class CategorySelector extends React.Component { return {createCategory ? categoryInput : categorySelect}; } } -export function validateCategory(category, required = true) { +export function validateCategory(category, maxTags, required = true) { if (!category || category.trim() === '') return required ? tt('g.required') : null; const cats = category.trim().split(' '); return ( // !category || category.trim() === '' ? 'Required' : - cats.length > MAX_TAG + cats.length > maxTags ? tt('category_selector_jsx.use_limited_amount_of_categories', { - amount: MAX_TAG, + amount: maxTags, }) : cats.find(c => c.length > 24) ? tt('category_selector_jsx.maximum_tag_length_is_24_characters') @@ -133,5 +131,6 @@ export default connect((state, ownProps) => { // apply translations // they are used here because default prop can't acces intl property const placeholder = tt('category_selector_jsx.tag_your_story'); - return { trending, placeholder, ...ownProps }; + const scotMaxTags = state.app.getIn(['hostConfig', 'APP_MAX_TAG']); + return { trending, placeholder, scotMaxTags, ...ownProps }; })(CategorySelector); diff --git a/src/app/components/cards/Comment.jsx b/src/app/components/cards/Comment.jsx index b6226b937..e966ef725 100644 --- a/src/app/components/cards/Comment.jsx +++ b/src/app/components/cards/Comment.jsx @@ -19,14 +19,13 @@ import tt from 'counterpart'; import { parsePayoutAmount } from 'app/utils/ParsersAndFormatters'; import { Long } from 'bytebuffer'; import ImageUserBlockList from 'app/utils/ImageUserBlockList'; -import { PREFER_HIVE, LIQUID_TOKEN_UPPERCASE } from 'app/client_config'; import ContentEditedWrapper from '../elements/ContentEditedWrapper'; import { allowDelete } from 'app/utils/StateFunctions'; import { Role } from 'app/utils/Community'; -export function sortComments(cont, comments, sort_order) { +export function sortComments(cont, comments, sort_order, scotTokenSymbol) { function totalPayout(a) { - const scotData = a.getIn(['scotData', LIQUID_TOKEN_UPPERCASE]); + const scotData = a.getIn(['scotData', scotTokenSymbol]); return scotData ? parseInt(scotData.get('pending_token')) + parseInt(scotData.get('total_payout_value')) + @@ -84,6 +83,7 @@ class CommentImpl extends React.Component { rootComment: PropTypes.string, anchor_link: PropTypes.string.isRequired, deletePost: PropTypes.func.isRequired, + scotTokenSymbol: PropTypes.string, }; constructor() { @@ -140,9 +140,14 @@ class CommentImpl extends React.Component { } // Client-side only when using componentDidMount - const { tribeMuteAccount, tribeIgnoreList, fetchFollows } = this.props; + const { + tribeMuteAccount, + tribeIgnoreList, + fetchFollows, + preferHive, + } = this.props; if (tribeMuteAccount && !tribeIgnoreList) { - fetchFollows(tribeMuteAccount); + fetchFollows(tribeMuteAccount, preferHive); } } @@ -214,6 +219,8 @@ class CommentImpl extends React.Component { depth, anchor_link, showNegativeComments, + scotTokenSymbol, + preferHive, ignored, rootComment, community, @@ -236,7 +243,7 @@ class CommentImpl extends React.Component { const author = post.get('author'); const comment = post.toJS(); - const gray = comment.stats.gray || ImageUserBlockList.includes(author); + const gray = (comment.stats && comment.stats.gray) || ImageUserBlockList.includes(author); const allowReply = Role.canComment(community, viewer_role); const canEdit = username && username === author; @@ -289,7 +296,12 @@ class CommentImpl extends React.Component { ); } else { replies = comment.replies.filter(c => cont.get(c)); - sortComments(cont, replies, this.props.sort_order); + sortComments( + cont, + replies, + this.props.sort_order, + scotTokenSymbol + ); replies = replies.map((reply, idx) => (
- +
@@ -366,9 +378,13 @@ class CommentImpl extends React.Component {
- +
- +
 {/* ·  */} { + fetchFollows: (account, preferHive) => { dispatch( fetchDataSagaActions.fetchFollows({ method: 'getFollowingAsync', account, type: 'ignore', - useHive: PREFER_HIVE, + useHive: preferHive, }) ); }, diff --git a/src/app/components/cards/MarkdownViewer.jsx b/src/app/components/cards/MarkdownViewer.jsx index b62b32a97..ce5f88797 100644 --- a/src/app/components/cards/MarkdownViewer.jsx +++ b/src/app/components/cards/MarkdownViewer.jsx @@ -37,6 +37,7 @@ class MarkdownViewer extends Component { hideImages: PropTypes.bool, // whether to replace images with just a span containing the src url breaks: PropTypes.bool, // true to use bastardized markdown that cares about newlines // used for the ImageUserBlockList + appDomain: PropTypes.string, }; static defaultProps = { @@ -65,7 +66,7 @@ class MarkdownViewer extends Component { }; render() { - const { noImage, hideImages } = this.props; + const { noImage, hideImages, appDomain, preferHive } = this.props; const { allowNoImage } = this.state; let { text } = this.props; if (!text) text = ''; // text can be empty, still view the link meta data @@ -102,7 +103,11 @@ class MarkdownViewer extends Component { // Embed videos, link mentions and hashtags, etc... if (renderedText) - renderedText = HtmlReady(renderedText, { hideImages }).html; + renderedText = HtmlReady(renderedText, { + hideImages, + appDomain, + useHive: preferHive, + }).html; // Complete removal of javascript and other dangerous tags.. // The must remain as close as possible to dangerouslySetInnerHTML @@ -117,6 +122,7 @@ class MarkdownViewer extends Component { large, highQualityPost, noImage: noImage && allowNoImage, + appDomain, }) ); } catch(e) { @@ -207,5 +213,7 @@ class MarkdownViewer extends Component { } export default connect((state, ownProps) => { - return { ...ownProps }; + const appDomain = state.app.getIn(['hostConfig', 'APP_DOMAIN']); + const preferHive = state.app.getIn(['hostConfig', 'PREFER_HIVE']); + return { appDomain, preferHive, ...ownProps }; })(MarkdownViewer); diff --git a/src/app/components/cards/PostFull.jsx b/src/app/components/cards/PostFull.jsx index b6bfc8a39..0db0275ec 100644 --- a/src/app/components/cards/PostFull.jsx +++ b/src/app/components/cards/PostFull.jsx @@ -1,3 +1,4 @@ +import { Map } from 'immutable'; import React from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router'; @@ -20,13 +21,6 @@ import MuteButton from 'app/components/elements/MuteButton'; import FlagButton from 'app/components/elements/FlagButton'; import { serverApiRecordEvent } from 'app/utils/ServerApiClient'; import Userpic from 'app/components/elements/Userpic'; -import { - APP_DOMAIN, - APP_NAME, - SHOW_AUTHOR_RECENT_POSTS, - POSTED_VIA_NITROUS_ICON, - LIQUID_TOKEN_UPPERCASE -} from 'app/client_config'; import tt from 'counterpart'; import { ifHivemind } from 'app/utils/Community'; import userIllegalContent from 'app/utils/userIllegalContent'; @@ -39,18 +33,18 @@ import UserNames from 'app/components/elements/UserNames'; import ContentEditedWrapper from '../elements/ContentEditedWrapper'; import { actions as fetchDataSagaActions } from 'app/redux/FetchDataSaga'; -function TimeAuthorCategory({ post, community }) { +function TimeAuthorCategory({ post, community, hive }) { return ( {tt('g.in')}{' '} {tt('g.by')}{' '} - + ); } -function TimeAuthorCategoryLarge({ post, community }) { +function TimeAuthorCategoryLarge({ post, community, hive }) { const crossPostedBy = post.get('cross_posted_by'); let author = post.get('author'); let created = post.get('created'); @@ -64,9 +58,14 @@ function TimeAuthorCategoryLarge({ post, community }) { return ( - +
- + {tt('g.in')} {' • '} {' '} @@ -88,6 +87,7 @@ class PostFull extends React.Component { // connector props username: PropTypes.string, + hostConfig: PropTypes.object, deletePost: PropTypes.func.isRequired, showPromotePost: PropTypes.func.isRequired, showExplorePost: PropTypes.func.isRequired, @@ -122,9 +122,10 @@ class PostFull extends React.Component { ); }; this.onTribeMute = () => { - const { username, tribeMute, post } = this.props; + const { username, hostConfig, tribeMute, post } = this.props; tribeMute( username, + hostConfig['LIQUID_TOKEN_UPPERCASE'], post.get('author'), post.get('permlink'), !post.get('muted'), @@ -213,6 +214,7 @@ class PostFull extends React.Component { } linkedInShare(e) { + const { hostConfig } = this.props; serverApiRecordEvent('LinkedInShare', this.share_params.link); e.preventDefault(); const winWidth = 720; @@ -225,7 +227,7 @@ class PostFull extends React.Component { encodeURIComponent(s.title) + '&url=' + encodeURIComponent(s.url) + - `&source=${APP_NAME}&mini=true`; + `&source=${hostConfig['APP_NAME']}&mini=true`; window.open( 'https://www.linkedin.com/shareArticle?' + q, 'Share', @@ -264,12 +266,14 @@ class PostFull extends React.Component { const account = post.get('author'); const permlink = post.get('permlink'); + const hive = post.get('hive'); this.props.togglePinnedPost( !isPinned, username, community, account, - permlink + permlink, + hive ); }; @@ -280,6 +284,7 @@ class PostFull extends React.Component { post, community, viewer_role, + hostConfig, tokenAccount, muteAccount, }, @@ -295,6 +300,13 @@ class PostFull extends React.Component { onDeletePost, onTribeMute, } = this; + const { + APP_NAME, + APP_DOMAIN, + POSTED_VIA_NITROUS_ICON, + COMMUNITY_CATEGORY, + SHOW_AUTHOR_RECENT_POSTS, + } = hostConfig; if (!post) return null; const communityName = community ? community.get('name') : null; const content = post.toJS(); @@ -512,7 +524,7 @@ class PostFull extends React.Component { const canReply = allowReply && post.get('depth') < 255; const canEdit = username === author && !showEdit; const canDelete = username === author && allowDelete(post); - const canTribeMute = username === tokenAccount || (muteAccount && username === muteAccount); + const canTribeMute = (tokenAccount && username === tokenAccount) || (muteAccount && username === muteAccount); const isPinned = post.getIn(['stats', 'is_pinned'], false); @@ -551,6 +563,7 @@ class PostFull extends React.Component {
@@ -574,12 +587,19 @@ class PostFull extends React.Component { {tt('g.promote')} )} - {!isReply && } + {!isReply && ( + + )}
@@ -682,6 +702,7 @@ export default connect( const muteAccount = state.app.getIn(['scotConfig', 'config', 'muting_account']); return { + hostConfig: state.app.get('hostConfig', Map()).toJS(), post, postref, community, @@ -705,7 +726,7 @@ export default connect( }) ); }, - tribeMute: (username, author, permlink, mute, hive) => { + tribeMute: (username, token, author, permlink, mute, hive) => { dispatch( transactionActions.broadcastOperation({ type: 'custom_json', @@ -713,7 +734,7 @@ export default connect( id: 'scot_mute_post', required_posting_auths: [username], json: JSON.stringify({ - token: LIQUID_TOKEN_UPPERCASE, + token, authorperm: `@${author}/${permlink}`, mute, }), @@ -749,7 +770,8 @@ export default connect( account, permlink, successCallback, - errorCallback + errorCallback, + hive ) => { let action = 'unpinPost'; if (pinPost) action = 'pinPost'; @@ -773,6 +795,7 @@ export default connect( }, successCallback, errorCallback, + useHive: hive, }) ); }, diff --git a/src/app/components/cards/PostFull.scss b/src/app/components/cards/PostFull.scss index 28ca90afa..bc319fe91 100644 --- a/src/app/components/cards/PostFull.scss +++ b/src/app/components/cards/PostFull.scss @@ -227,7 +227,9 @@ cursor: pointer; &:hover { svg { - fill: $color-teal; + @include themify($themes) { + fill: themed('colorAccent'); + } } } } diff --git a/src/app/components/cards/PostSummary.jsx b/src/app/components/cards/PostSummary.jsx index 2864dd8e8..14e1fcb33 100644 --- a/src/app/components/cards/PostSummary.jsx +++ b/src/app/components/cards/PostSummary.jsx @@ -20,18 +20,14 @@ import { ifHivemind } from 'app/utils/Community'; import ImageUserBlockList from 'app/utils/ImageUserBlockList'; import { proxifyImageUrl } from 'app/utils/ProxifyUrl'; import Userpic, { SIZE_SMALL } from 'app/components/elements/Userpic'; -import { SIGNUP_URL } from 'shared/constants'; -import { - APP_NAME, - INTERLEAVE_PROMOTED, - POSTED_VIA_NITROUS_ICON, -} from 'app/client_config'; +import { HIVE_SIGNUP_URL, SIGNUP_URL } from 'shared/constants'; import { hasNsfwTag } from 'app/utils/StateFunctions'; const CURATOR_VESTS_THRESHOLD = 1.0 * 1000.0 * 1000.0; // TODO: document why ` ` => `%20` is needed, and/or move to base fucntion -const proxify = (url, size) => proxifyImageUrl(url, size).replace(/ /g, '%20'); +const proxify = (url, hive, size) => + proxifyImageUrl(url, hive, size).replace(/ /g, '%20'); const vote_weights = post => { const rshares = post.get('net_rshares'); @@ -48,6 +44,7 @@ class PostSummary extends React.Component { onClose: PropTypes.func, nsfwPref: PropTypes.string, promoted: PropTypes.object, + interleavePromoted: PropTypes.bool, }; constructor() { @@ -94,6 +91,10 @@ class PostSummary extends React.Component { promoted, featured, featuredOnClose, + appName, + appDomain, + nitrousPostedIcon, + interleavePromoted, } = this.props; const { account } = this.props; let requestedCategory; @@ -155,7 +156,7 @@ class PostSummary extends React.Component { const gray = post.getIn(['stats', 'gray']); const isNsfw = hasNsfwTag(post); const isPromoted = - INTERLEAVE_PROMOTED && + interleavePromoted && promoted && promoted.contains(`${post.get('author')}/${post.get('permlink')}`); const hive = post.get('hive'); @@ -212,6 +213,7 @@ class PostSummary extends React.Component {
@@ -221,6 +223,7 @@ class PostSummary extends React.Component { - {POSTED_VIA_NITROUS_ICON && + {nitrousPostedIcon && app_info.startsWith( - `${APP_NAME.toLowerCase()}/` + `${appName.toLowerCase()}/` ) && ( - + )} @@ -366,7 +369,11 @@ class PostSummary extends React.Component { ) : ( - + {tt( 'postsummary_jsx.create_an_account' )} @@ -386,12 +393,16 @@ class PostSummary extends React.Component { let image_link = extractImageLink( post.get('json_metadata'), + appDomain, + hive, post.get('body') ); if (crossPostedBy) { image_link = extractImageLink( post.get('cross_post_json_metadata'), + appDomain, + hive, post.get('cross_post_body') ); } @@ -405,8 +416,8 @@ class PostSummary extends React.Component { }/avatar/medium`; listImgLarge = `https://images.hive.blog/u/${author}/avatar/large`; } else if (image_link) { - listImgMedium = proxify(image_link, '256x512'); - listImgLarge = proxify(image_link, '640x480'); + listImgMedium = proxify(image_link, hive, '256x512'); + listImgLarge = proxify(image_link, hive, '640x480'); } let thumb = null; @@ -487,6 +498,16 @@ export default connect( state.user.getIn(['current', 'username']) || state.offchain.get('account'), blogmode: state.app.getIn(['user_preferences', 'blogmode']), + appName: state.app.getIn(['hostConfig', 'APP_NAME']), + appDomain: state.app.getIn(['hostConfig', 'APP_DOMAIN']), + nitrousPostedIcon: state.app.getIn([ + 'hostConfig', + 'POSTED_VIA_NITROUS_ICON', + ]), + interleavePromoted: state.app.getIn( + ['hostConfig', 'INTERLEAVE_PROMOTED'], + false + ), nsfwPref, net_vests, }; diff --git a/src/app/components/cards/TagInput.jsx b/src/app/components/cards/TagInput.jsx index 7cea97af8..a17ac3137 100644 --- a/src/app/components/cards/TagInput.jsx +++ b/src/app/components/cards/TagInput.jsx @@ -6,10 +6,6 @@ import { cleanReduxInput } from 'app/utils/ReduxForms'; import tt from 'counterpart'; import { List } from 'immutable'; -import { APP_MAX_TAG } from 'app/client_config'; - -const MAX_TAGS = APP_MAX_TAG || 10; - class TagInput extends React.Component { static propTypes = { // HTML props @@ -69,7 +65,7 @@ class TagInput extends React.Component { return {input}; } } -export function validateTagInput(value, required = true) { +export function validateTagInput(value, maxTags, required = true) { if (!value || value.trim() === '') return required ? tt('g.required') : null; const cats = value @@ -79,9 +75,9 @@ export function validateTagInput(value, required = true) { return ( // !value || value.trim() === '' ? 'Required' : - cats.length > MAX_TAGS + cats.length > maxTags ? tt('category_selector_jsx.use_limited_amount_of_categories', { - amount: MAX_TAGS, + amount: maxTags, }) : cats.find(c => c.length > 24) ? tt('category_selector_jsx.maximum_tag_length_is_24_characters') diff --git a/src/app/components/cards/TransferHistoryRow/index.jsx b/src/app/components/cards/TransferHistoryRow/index.jsx index 78ca6edb9..56eb14223 100644 --- a/src/app/components/cards/TransferHistoryRow/index.jsx +++ b/src/app/components/cards/TransferHistoryRow/index.jsx @@ -7,10 +7,9 @@ import Memo from 'app/components/elements/Memo'; import { numberWithCommas } from 'app/utils/StateFunctions'; import tt from 'counterpart'; import GDPRUserList from 'app/utils/GDPRUserList'; -import { APP_URL, LIQUID_TOKEN_UPPERCASE } from 'app/client_config'; -function formatScotAmount(quantity, precision) { - return (quantity / Math.pow(10, precision)).toFixed(precision); +function formatScotAmount(quantity) { + return quantity; } const postLink = (socialUrl, author, permlink) => ( @@ -21,7 +20,7 @@ const postLink = (socialUrl, author, permlink) => ( class TransferHistoryRow extends React.Component { render() { - const { op, context, scotTokenSymbol } = this.props; + const { op, context, scotTokenSymbol, appUrl } = this.props; // context -> account perspective /* all transfers involve up to 2 accounts, context and 1 other. */ @@ -120,8 +119,7 @@ class TransferHistoryRow extends React.Component { {tt(['transferhistoryrow_jsx', 'staking_reward'], { amount: `${formatScotAmount( - op.int_amount, - op.precision + op.quantity, )} ${scotTokenSymbol}`, })} @@ -136,12 +134,11 @@ class TransferHistoryRow extends React.Component { {tt(['transferhistoryrow_jsx', op.type], { amount: `${formatScotAmount( - op.int_amount, - op.precision + op.quantity, )} ${scotTokenSymbol}`, })} {op.type != 'mining_reward' && - postLink(APP_URL, op.author, op.permlink)} + postLink(appUrl, op.author, op.permlink)} ); } else { @@ -179,9 +176,13 @@ const otherAccountLink = username => export default connect( // mapStateToProps (state, ownProps) => { - const scotTokenSymbol = LIQUID_TOKEN_UPPERCASE; + const scotTokenSymbol = state.app.getIn([ + 'hostConfig', + 'LIQUID_TOKEN_UPPERCASE', + ]); return { ...ownProps, + appUrl: state.app.getIn(['hostConfig', 'APP_URL']), scotTokenSymbol, }; } diff --git a/src/app/components/cards/UserProfileHeader.jsx b/src/app/components/cards/UserProfileHeader.jsx index 25834f4d2..78000114c 100644 --- a/src/app/components/cards/UserProfileHeader.jsx +++ b/src/app/components/cards/UserProfileHeader.jsx @@ -14,7 +14,6 @@ import SanitizedLink from 'app/components/elements/SanitizedLink'; import { numberWithCommas } from 'app/utils/StateFunctions'; import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper'; import DropdownMenu from 'app/components/elements/DropdownMenu'; -import { COMMUNITY_CATEGORY, DISABLE_BLACKLIST } from 'app/client_config'; import { actions as fetchActions } from 'app/redux/FetchDataSaga'; class UserProfileHeader extends React.Component { @@ -31,6 +30,8 @@ class UserProfileHeader extends React.Component { accountname, profile, tribeCommunityTitle, + scotTokenSymbol, + preferHive, disableBlacklist, stakedAccounts, } = this.props; @@ -47,7 +48,9 @@ class UserProfileHeader extends React.Component { if (cover_image) { cover_image_style = { backgroundImage: - 'url(' + proxifyImageUrl(cover_image, '2048x512') + ')', + 'url(' + + proxifyImageUrl(cover_image, preferHive, '2048x512') + + ')', }; } @@ -69,6 +72,7 @@ class UserProfileHeader extends React.Component { const affiliation = tribeCommunityTitle ? tribeCommunityTitle : affiliationFromStake( + scotTokenSymbol, accountname, stakedAccounts ? stakedAccounts.get(accountname) : 0 ); @@ -84,7 +88,11 @@ class UserProfileHeader extends React.Component {

- + {name || accountname} {blacklists} {affiliation ? ( {affiliation} @@ -159,6 +167,11 @@ export default connect( (state, props) => { const current_user = state.user.getIn(['current', 'username']); const accountname = props.accountname; + + const COMMUNITY_CATEGORY = state.app.getIn([ + 'hostConfig', + 'COMMUNITY_CATEGORY', + ]); const tribeCommunityTitle = state.global.getIn([ 'community', COMMUNITY_CATEGORY, @@ -166,12 +179,23 @@ export default connect( accountname, 'title', ]); + const scotTokenSymbol = state.app.getIn([ + 'hostConfig', + 'LIQUID_TOKEN_UPPERCASE', + ]); + const disableBlacklist = state.app.getIn([ + 'hostConfig', + 'DISABLE_BLACKLIST', + ]); + const preferHive = state.app.getIn(['hostConfig', 'PREFER_HIVE']); return { current_user, accountname, profile: props.profile, tribeCommunityTitle, - disableBlacklist: DISABLE_BLACKLIST, + scotTokenSymbol, + preferHive, + disableBlacklist, stakedAccounts: state.global.get('stakedAccounts'), }; }, diff --git a/src/app/components/elements/AppLogo/index.jsx b/src/app/components/elements/AppLogo/index.jsx index 83efc5508..119d54049 100644 --- a/src/app/components/elements/AppLogo/index.jsx +++ b/src/app/components/elements/AppLogo/index.jsx @@ -1,10 +1,37 @@ +import { Map } from 'immutable'; import React from 'react'; import PropTypes from 'prop-types'; import SvgImage from 'app/components/elements/SvgImage'; -import { APP_ICON } from 'app/client_config'; -const AppLogo = () => { - return ; -}; +class AppLogo extends React.Component { + static propTypes = { + name: PropTypes.string.isRequired, + width: PropTypes.string.isRequired, + height: PropTypes.string.isRequired, + }; -export default AppLogo; + render() { + const { name, width, height } = this.props; + return ; + } +} + +import { connect } from 'react-redux'; + +export default connect((state, ownProps) => { + const hostConfig = state.app.get('hostConfig', Map()).toJS(); + let name = hostConfig['APP_ICON']; + let width = hostConfig['APP_ICON_WIDTH']; + let height = hostConfig['APP_ICON_HEIGHT']; + if (ownProps.width) { + width = ownProps.width; + } + if (ownProps.height) { + height = ownProps.height; + } + return { + name, + width, + height, + }; +})(AppLogo); diff --git a/src/app/components/elements/Author/index.jsx b/src/app/components/elements/Author/index.jsx index 548ccc519..3bac5f071 100644 --- a/src/app/components/elements/Author/index.jsx +++ b/src/app/components/elements/Author/index.jsx @@ -13,7 +13,6 @@ import Reputation from 'app/components/elements/Reputation'; import { affiliationFromStake } from 'app/utils/AffiliationMap'; import UserTitle from 'app/components/elements/UserTitle'; import AuthorDropdown from '../AuthorDropdown'; -import { COMMUNITY_CATEGORY, DISABLE_BLACKLIST } from 'app/client_config'; import { actions as fetchActions } from 'app/redux/FetchDataSaga'; const { string, bool, number } = PropTypes; @@ -126,6 +125,8 @@ class Author extends React.Component { follow, mute, showAffiliation, + scotTokenSymbol, + hive, blacklists, showRole, community, @@ -145,6 +146,7 @@ class Author extends React.Component { const affiliation = tribeCommunityTitle ? tribeCommunityTitle : affiliationFromStake( + scotTokenSymbol, author, stakedAccounts ? stakedAccounts.get(author) : 0 ); @@ -221,6 +223,7 @@ class Author extends React.Component { mute={mute} authorRep={authorRep} username={username} + hive={hive} blacklists={blacklists} /> @@ -242,6 +245,15 @@ export default connect( authorRep = post.get('cross_post_author_reputation'); } + const scotTokenSymbol = state.app.getIn([ + 'hostConfig', + 'LIQUID_TOKEN_UPPERCASE', + ]); + const COMMUNITY_CATEGORY = state.app.getIn([ + 'hostConfig', + 'COMMUNITY_CATEGORY', + ]); + const tribeCommunityTitle = state.global.getIn([ 'community', COMMUNITY_CATEGORY, @@ -249,7 +261,10 @@ export default connect( author, 'title', ]); - const disableBlacklist = DISABLE_BLACKLIST; + const disableBlacklist = state.app.getIn([ + 'hostConfig', + 'DISABLE_BLACKLIST', + ]); return { follow: typeof props.follow === 'undefined' ? true : props.follow, @@ -267,6 +282,7 @@ export default connect( crossPostAuthor: post.get('cross_post_author'), showRole: props.showRole, tribeCommunityTitle, + scotTokenSymbol, stakedAccounts: state.global.get('stakedAccounts'), }; }, diff --git a/src/app/components/elements/AuthorDropdown.jsx b/src/app/components/elements/AuthorDropdown.jsx index 2c6568dd8..e3931f46b 100644 --- a/src/app/components/elements/AuthorDropdown.jsx +++ b/src/app/components/elements/AuthorDropdown.jsx @@ -63,7 +63,7 @@ class AuthorDropdown extends Component {
- + {name && ( diff --git a/src/app/components/elements/ElasticSearchInput/index.jsx b/src/app/components/elements/ElasticSearchInput/index.jsx index 1f154c7b4..f01757d71 100644 --- a/src/app/components/elements/ElasticSearchInput/index.jsx +++ b/src/app/components/elements/ElasticSearchInput/index.jsx @@ -19,7 +19,7 @@ class ElasticSearchInput extends React.Component { constructor(props) { super(props); this.state = { - value: this.props.initValue ? this.props.initValue : '', + value: this.props.initValue, }; this.handleChange = this.handleChange.bind(this); this.onSearchSubmit = this.onSearchSubmit.bind(this); @@ -33,7 +33,8 @@ class ElasticSearchInput extends React.Component { e.preventDefault(); const { handleSubmit, redirect } = this.props; handleSubmit && handleSubmit(this.state.value); - redirect && browserHistory.push(`/search?q=${this.state.value}`); + redirect && + browserHistory.push(`/search?q=${this.state.value} +build-it.io`); }; render() { @@ -42,7 +43,13 @@ class ElasticSearchInput extends React.Component { : 'search-input'; return ( -
+ +
); diff --git a/src/app/components/elements/ElasticSearchInput/styles.scss b/src/app/components/elements/ElasticSearchInput/styles.scss index a7d0827d4..06d7b0407 100644 --- a/src/app/components/elements/ElasticSearchInput/styles.scss +++ b/src/app/components/elements/ElasticSearchInput/styles.scss @@ -10,7 +10,7 @@ form.search-input { } stroke-width: 1.2; fill: none; - + } input.search-input__inner { @@ -29,7 +29,7 @@ form.search-input { transition: all 0.3s ease-in-out; font-family: $font-primary; border: 1px solid rgba(173, 173, 173, 0.6); - + &::placeholder { color: transparent } @@ -59,9 +59,4 @@ form.search-input { } } } - - /* small */ - input.search-input__inner.search-input__inner--small { - - } -} \ No newline at end of file +} diff --git a/src/app/components/elements/Follow/index.jsx b/src/app/components/elements/Follow/index.jsx index c50d2b4c9..350cd905a 100644 --- a/src/app/components/elements/Follow/index.jsx +++ b/src/app/components/elements/Follow/index.jsx @@ -177,7 +177,7 @@ module.exports = connect( ? 'ignore' : null; - const useHive = PREFER_HIVE; + const useHive = state.app.getIn(['hostConfig', 'PREFER_HIVE']); return { follower, diff --git a/src/app/components/elements/Icon.jsx b/src/app/components/elements/Icon.jsx index 4ca2b30e4..acb84cc11 100644 --- a/src/app/components/elements/Icon.jsx +++ b/src/app/components/elements/Icon.jsx @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { POSTED_VIA_NITROUS_ICON } from 'app/client_config'; export const icons = [ 'user', 'share', 'chevron-up-circle', + 'chevron-up-circle-red', 'chevron-down-circle', 'chevron-left', 'chatboxes', @@ -58,7 +58,8 @@ export const icons = [ 'pencil2', 'pin', 'pin-disabled', -].concat(POSTED_VIA_NITROUS_ICON ? [POSTED_VIA_NITROUS_ICON] : []); + 'weedcash', +]; const icons_map = {}; for (const i of icons) icons_map[i] = require(`assets/icons/${i}.svg`); diff --git a/src/app/components/elements/IconButton/styles.scss b/src/app/components/elements/IconButton/styles.scss index 3ac9cf747..0120a553a 100644 --- a/src/app/components/elements/IconButton/styles.scss +++ b/src/app/components/elements/IconButton/styles.scss @@ -27,7 +27,9 @@ } } &.icon-button__svg--green { - fill: $color-teal; + @include themify($themes) { + background-color: themed('colorAccent'); + } } &.icon-button__svg--transparent { fill: transparent; @@ -42,7 +44,7 @@ } &.icon-button__border--green{ @include themify($themes) { - stroke: $color-teal; + stroke: themed('colorAccent'); } } &.icon-button--transparent { diff --git a/src/app/components/elements/Link.js b/src/app/components/elements/Link.js index 02e7701c5..108570c14 100644 --- a/src/app/components/elements/Link.js +++ b/src/app/components/elements/Link.js @@ -4,16 +4,18 @@ import links from 'app/utils/Links'; import { browserHistory } from 'react-router'; import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; -export default class Link extends React.Component { +class Link extends React.Component { static propTypes = { // HTML properties href: PropTypes.string, + // Redux properties + appDomain: PropTypes.string, }; constructor(props) { super(); - const { href } = props; + const { href, appDomain } = props; this.shouldComponentUpdate = shouldComponentUpdate(this, 'Link'); - this.localLink = href && links.local.test(href); + this.localLink = href && links.local(appDomain).test(href); this.onLocalClick = e => { e.preventDefault(); browserHistory.push(this.props.href); @@ -29,3 +31,16 @@ export default class Link extends React.Component { ); } } + +import { connect } from 'react-redux'; + +export default connect( + // mapStateToProps + (state, ownProps) => { + const appDomain = state.app.getIn(['hostConfig', 'APP_DOMAIN']); + return { + ...ownProps, + appDomain, + }; + } +)(Link); diff --git a/src/app/components/elements/LoadingIndicator.scss b/src/app/components/elements/LoadingIndicator.scss index 05664a849..735550bd4 100644 --- a/src/app/components/elements/LoadingIndicator.scss +++ b/src/app/components/elements/LoadingIndicator.scss @@ -138,9 +138,11 @@ padding-bottom:2em; div { border-width:1px; - border-color: $color-teal; - border-right-color: transparent; - border-top-color: transparent; + @include themify($themes) { + border-color: themed('colorAccent'); + border-right-color: transparent; + border-top-color: transparent; + } } } @@ -148,10 +150,12 @@ width: $circle-radius; height: $circle-radius; margin: 4px; - border: 1px solid $color-teal; + @include themify($themes) { + border: 1px solid themed('colorAccent'); + border-right-color: transparent; + border-top-color: transparent; + } border-radius: 50%; - border-right-color: transparent; - border-top-color: transparent; animation: loading 500ms infinite linear; } } diff --git a/src/app/components/elements/Notices.jsx b/src/app/components/elements/Notices.jsx index 6a9e0e76b..4fa069ae8 100644 --- a/src/app/components/elements/Notices.jsx +++ b/src/app/components/elements/Notices.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Map } from 'immutable'; import tt from 'counterpart'; import { Link } from 'react-router'; import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper'; @@ -71,7 +72,13 @@ const SteemitNotices = ({ notices }) => { module.exports = connect(state => ({ notices: state.offchain - .get('pinned_posts') - .get('notices') + .getIn( + [ + 'pinned_posts', + state.app.getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']), + 'notices', + ], + Map() + ) .toJS(), }))(SteemitNotices); diff --git a/src/app/components/elements/Reblog.scss b/src/app/components/elements/Reblog.scss index ed0f61a8d..8b2c66863 100644 --- a/src/app/components/elements/Reblog.scss +++ b/src/app/components/elements/Reblog.scss @@ -17,10 +17,12 @@ .Reblog__button.loading { padding-right: 0.5rem; svg { - border: 1px solid $color-teal; + @include themify($themes) { + border: 1px solid themed('colorAccent'); + border-right-color: transparent; + border-top-color: transparent; + } border-radius: 100%; - border-right-color: transparent; - border-top-color: transparent; animation: loading 500ms infinite linear; path {opacity: 0} } diff --git a/src/app/components/elements/ReplyEditor.jsx b/src/app/components/elements/ReplyEditor.jsx index c7ae988e7..fe2455e03 100644 --- a/src/app/components/elements/ReplyEditor.jsx +++ b/src/app/components/elements/ReplyEditor.jsx @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import reactForm from 'app/utils/ReactForm'; -import { Map } from 'immutable'; import _ from 'lodash'; import classNames from 'classnames'; import { connect } from 'react-redux'; @@ -25,39 +24,33 @@ import sanitizeConfig, { allowedTags } from 'app/utils/SanitizeConfig'; import sanitize from 'sanitize-html'; import HtmlReady from 'shared/HtmlReady'; import * as globalActions from 'app/redux/GlobalReducer'; -import { fromJS, Set, OrderedSet } from 'immutable'; +import { fromJS, Map, Set, OrderedSet } from 'immutable'; import Remarkable from 'remarkable'; import Dropzone from 'react-dropzone'; import tt from 'counterpart'; -import { - APP_NAME, - COMMUNITY_CATEGORY, - SCOT_TAG, - SCOT_TAG_FIRST, - APP_MAX_TAG, - SCOT_DEFAULT_BENEFICIARY_ACCOUNT, - SCOT_DEFAULT_BENEFICIARY_PERCENT, - PREFER_HIVE, -} from 'app/client_config'; import { loadUserTemplates, saveUserTemplates } from 'app/utils/UserTemplates'; const remarkable = new Remarkable({ html: true, linkify: false, breaks: true }); const RTE_DEFAULT = false; -const MAX_TAG = APP_MAX_TAG || 10; const MAX_FILE_TO_UPLOAD = 10; let imagesToUpload = []; -function allTags(userInput, originalCategory, hashtags) { +function allTags(userInput, originalCategory, hashtags, hostConfig) { // take space-delimited user input let tags = OrderedSet( - userInput ? userInput.trim().replace(/#/g, '').split(/ +/) : [] + userInput + ? userInput + .trim() + .replace(/#/g, '') + .split(/ +/) + : [] ); - if (SCOT_TAG_FIRST) { - tags = OrderedSet([SCOT_TAG, ...tags.toJS()]); + if (hostConfig['SCOT_TAG_FIRST']) { + tags = OrderedSet([hostConfig['SCOT_TAG'], ...tags.toJS()]); } else { - tags = tags.add(SCOT_TAG); + tags = tags.add(hostConfig['SCOT_TAG']); } // remove original category, if present @@ -66,7 +59,7 @@ function allTags(userInput, originalCategory, hashtags) { // append hashtags from post until limit is reached const tagged = [...hashtags]; - while (tags.size < MAX_TAG && tagged.length > 0) { + while (tags.size < hostConfig['APP_MAX_TAG'] && tagged.length > 0) { tags = tags.add(tagged.shift()); } return tags; @@ -90,6 +83,7 @@ class ReplyEditor extends React.Component { body: PropTypes.string, // initial value defaultPayoutType: PropTypes.string, payoutType: PropTypes.string, + hostConfig: PropTypes.object, postTemplateName: PropTypes.string, maxAcceptedPayout: PropTypes.number, }; @@ -184,7 +178,7 @@ class ReplyEditor extends React.Component { this.props.defaultBeneficiaries.toArray().length > 0 && this.props.referralSystem != 'disabled' ) { - this.props.defaultBeneficiaries.toArray().forEach((element) => { + this.props.defaultBeneficiaries.toArray().forEach(element => { const label = element.get('label'); const name = element.get('name'); const weight = parseInt(element.get('weight')); @@ -200,11 +194,11 @@ class ReplyEditor extends React.Component { ) { if ( qualifiedBeneficiaries.find( - (beneficiary) => beneficiary.username === name + beneficiary => beneficiary.username === name ) ) { qualifiedBeneficiaries.find( - (beneficiary) => beneficiary.username === name + beneficiary => beneficiary.username === name ).percent += parseInt((weight / 100).toFixed(0)); } else { qualifiedBeneficiaries.push({ @@ -372,7 +366,7 @@ class ReplyEditor extends React.Component { } initForm(props) { - const { isStory, type, fields } = props; + const { isStory, type, fields, hostConfig } = props; const isEdit = type === 'edit'; const maxKb = isStory ? 64 : 16; reactForm({ @@ -380,7 +374,7 @@ class ReplyEditor extends React.Component { instance: this, name: 'replyForm', initialValues: props.initialValues, - validation: (values) => { + validation: values => { const markdownRegex = /(?:\*[\w\s]*\*|\#[\w\s]*\#|_[\w\s]*_|~[\w\s]*~|\]\s*\(|\]\s*\[)/; let bodyValidation = null; if (!values.body) { @@ -390,9 +384,8 @@ class ReplyEditor extends React.Component { values.body && new Blob([values.body]).size >= maxKb * 1024 - 256 ) { - bodyValidation = `Post body exceeds ${ - maxKb * 1024 - 256 - } bytes.`; + bodyValidation = `Post body exceeds ${maxKb * 1024 - + 256} bytes.`; } return { title: @@ -400,11 +393,17 @@ class ReplyEditor extends React.Component { (!values.title || values.title.trim() === '' ? tt('g.required') : values.title.length > 255 - ? tt('reply_editor.shorten_title') - : markdownRegex.test(values.title) - ? tt('reply_editor.markdown_not_supported') - : null), - tags: isStory && validateTagInput(values.tags, !isEdit), + ? tt('reply_editor.shorten_title') + : markdownRegex.test(values.title) + ? tt('reply_editor.markdown_not_supported') + : null), + tags: + isStory && + validateTagInput( + values.tags, + hostConfig['APP_MAX_TAG'], + !isEdit + ), body: bodyValidation, }; }, @@ -426,7 +425,7 @@ class ReplyEditor extends React.Component { title.props.onChange(e); }; - onCancel = (e) => { + onCancel = e => { if (e) e.preventDefault(); const { formId, onCancel, defaultPayoutType } = this.props; const { replyForm, body } = this.state; @@ -446,21 +445,24 @@ class ReplyEditor extends React.Component { }; // As rte_editor is updated, keep the (invisible) 'body' field in sync. - onChange = (rte_value) => { + onChange = rte_value => { this.refs.rte.setState({ state: rte_value }); const html = stateToHtml(rte_value); const { body } = this.state; if (body.value !== html) body.props.onChange(html); }; - toggleRte = (e) => { + toggleRte = e => { e.preventDefault(); + const hostConfig = this.props.hostConfig; + const appDomain = hostConfig['APP_DOMAIN']; + const preferHive = hostConfig['PREFER_HIVE']; const state = { rte: !this.state.rte }; if (state.rte) { const { body } = this.state; state.rte_value = isHtmlTest(body.value) ? stateFromHtml(body.value) - : stateFromMarkdown(body.value); + : stateFromMarkdown(body.value, appDomain, preferHive); } this.setState(state); localStorage.setItem('replyEditorData-rte', !this.state.rte); @@ -474,7 +476,7 @@ class ReplyEditor extends React.Component { } } - showAdvancedSettings = (e) => { + showAdvancedSettings = e => { e.preventDefault(); this.props.setPayoutType(this.props.formId, this.props.payoutType); @@ -491,7 +493,7 @@ class ReplyEditor extends React.Component { this.props.showAdvancedSettings(this.props.formId); }; - displayErrorMessage = (message) => { + displayErrorMessage = message => { this.setState({ progress: { error: message }, }); @@ -535,7 +537,7 @@ class ReplyEditor extends React.Component { this.dropzone.open(); }; - onPasteCapture = (e) => { + onPasteCapture = e => { try { if (e.clipboardData) { // @TODO: currently it seems to capture only one file, try to find a fix for multiple files @@ -579,7 +581,9 @@ class ReplyEditor extends React.Component { if (imageToUpload.temporaryTag === '') { imagesUploadCount++; - imageToUpload.temporaryTag = `![Uploading image #${imagesUploadCount}...]()`; + imageToUpload.temporaryTag = `![Uploading image #${ + imagesUploadCount + }...]()`; placeholder += `\n${imageToUpload.temporaryTag}\n`; } } @@ -594,13 +598,13 @@ class ReplyEditor extends React.Component { ); }; - upload = (image) => { + upload = image => { const { uploadImage } = this.props; this.setState({ progress: { message: tt('reply_editor.uploading') }, }); - uploadImage(image.file, (progress) => { + uploadImage(image.file, progress => { const { body } = this.state; if (progress.url) { @@ -659,9 +663,11 @@ class ReplyEditor extends React.Component { defaultPayoutType, payoutType, beneficiaries, - hive, + hostConfig, maxAcceptedPayout, } = this.props; + const hive = this.props.hive !== false && hostConfig['PREFER_HIVE']; + const appDomain = hostConfig['APP_DOMAIN']; const { submitting, valid, @@ -683,10 +689,10 @@ class ReplyEditor extends React.Component { // This will be used to display the cover image selector. let rtags; if (isStory) { - rtags = extractRtags(body.value); + rtags = extractRtags(appDomain, hive, body.value); } - const errorCallback = (estr) => { + const errorCallback = estr => { this.setState({ postError: estr, loading: false }); }; const isEdit = type === 'edit'; @@ -717,6 +723,7 @@ class ReplyEditor extends React.Component { maxAcceptedPayout, successCallback: successCallbackWrapper, errorCallback, + hostConfig, useHive: hive, }; const postLabel = @@ -755,7 +762,7 @@ class ReplyEditor extends React.Component { }); }; - const onSelectCoverImage = (event) => { + const onSelectCoverImage = event => { const { target } = event; const postImages = document.getElementsByClassName( @@ -787,15 +794,17 @@ class ReplyEditor extends React.Component { 'large-6': enableSideBySide, })} > - {isStory && !isEdit && username && ( - - )} + {isStory && + !isEdit && + username && ( + + )}
{titleError} @@ -904,7 +914,7 @@ class ReplyEditor extends React.Component { disableClick multiple accept="image/*" - ref={(node) => { + ref={node => { this.dropzone = node; }} > @@ -1010,7 +1020,7 @@ class ReplyEditor extends React.Component {

{Array.from(rtags.images).map( - (image) => { + image => { return (
- {isStory && !isEdit && ( -
-
{tt('reply_editor.post_options')}:
-
- {this.props.maxAcceptedPayout !== - null && - this.props.maxAcceptedPayout !== - 0 && ( -
- {tt( - 'post_advanced_settings_jsx.max_accepted_payout' - )} - {': '} - { - this.props - .maxAcceptedPayout - }{' '} - HBD -
- )} -
- {tt('g.rewards')} - {': '} - {this.props.payoutType === '0%' && - tt( - 'reply_editor.decline_payout' - )} - {this.props.payoutType === '50%' && - tt( - 'reply_editor.default_50_50' - )} - {this.props.payoutType === '100%' && - tt('reply_editor.power_up_100')} -
+ {isStory && + !isEdit && ( +
+
+ {tt('reply_editor.post_options')}: +
- {beneficiaries && - beneficiaries.length > 0 && ( - - {tt('g.beneficiaries')} - {': '} + {this.props.maxAcceptedPayout !== + null && + this.props.maxAcceptedPayout !== + 0 && ( +
{tt( - 'reply_editor.beneficiaries_set', - { - count: - beneficiaries.length, - } + 'post_advanced_settings_jsx.max_accepted_payout' )} - + {': '} + { + this.props + .maxAcceptedPayout + }{' '} +
+ )} +
+ {tt('g.rewards')} + {': '} + {this.props.payoutType === + '0%' && + tt( + 'reply_editor.decline_payout' + )} + {this.props.payoutType === + '50%' && + tt( + 'reply_editor.default_50_50' + )} + {this.props.payoutType === + '100%' && + tt( + 'reply_editor.power_up_100' + )} +
+
+ {beneficiaries && + beneficiaries.length > + 0 && ( + + {tt( + 'g.beneficiaries' + )} + {': '} + {tt( + 'reply_editor.beneficiaries_set', + { + count: + beneficiaries.length, + } + )} + + )} +
+ + {tt( + 'reply_editor.advanced_settings' )} + {' '} +
+  
- - {tt( - 'reply_editor.advanced_settings' - )} - {' '} -
-  
-
- )} + )}
{postError && ( @@ -1130,26 +1154,28 @@ class ReplyEditor extends React.Component { )}  {' '} - {!loading && this.props.onCancel && ( - - )} - {!loading && !this.props.onCancel && ( - - )} + {!loading && + this.props.onCancel && ( + + )} + {!loading && + !this.props.onCancel && ( + + )} {!isStory && !isEdit && this.props.payoutType != '50%' && ( @@ -1192,17 +1218,22 @@ class ReplyEditor extends React.Component {
)}
- {!loading && !rte && body.value && ( -
- -
- )} + {!loading && + !rte && + body.value && ( +
+ +
+ )}
); @@ -1217,7 +1248,7 @@ function stripHtmlWrapper(text) { return m && m.length === 2 ? m[1] : text; } // See also MarkdownViewer render -const isHtmlTest = (text) => /^/.test(text); +const isHtmlTest = text => /^/.test(text); function stateToHtml(state) { let html = serializeHtml(state); @@ -1234,19 +1265,18 @@ function stateFromHtml(html = null) { return html ? deserializeHtml(html) : getDemoState(); } -//var htmlclean = require('htmlclean'); -function stateFromMarkdown(markdown) { +function stateFromMarkdown(markdown, appDomain, preferHive) { let html; if (markdown && markdown.trim() !== '') { html = remarkable.render(markdown); - html = HtmlReady(html).html; // TODO: option to disable youtube conversion, @-links, img proxy + html = HtmlReady(html, { appDomain, useHive: preferHive }).html; // TODO: option to disable youtube conversion, @-links, img proxy //html = htmlclean(html) // normalize whitespace console.log('markdown converted to:', html); } return stateFromHtml(html); } -export default (formId) => +export default formId => connect( // mapStateToProps (state, ownProps) => { @@ -1278,8 +1308,8 @@ export default (formId) => const jsonMetadata = ownProps.jsonMetadata ? ownProps.jsonMetadata instanceof Map - ? ownProps.jsonMetadata.toJS() - : ownProps.jsonMetadata + ? ownProps.jsonMetadata.toJS() + : ownProps.jsonMetadata : {}; let tags = category; @@ -1342,6 +1372,7 @@ export default (formId) => ]); beneficiaries = beneficiaries ? beneficiaries.toJS() : []; + const hostConfig = state.app.get('hostConfig', Map()).toJS(); // Post full /* const replyParams = { @@ -1378,14 +1409,15 @@ export default (formId) => maxAcceptedPayout, initialValues: { title, body, tags }, formId, + hostConfig, }; }, // mapDispatchToProps - (dispatch) => ({ + dispatch => ({ uploadImage: (file, progress) => dispatch(userActions.uploadImage({ file, progress })), - showAdvancedSettings: (formId) => + showAdvancedSettings: formId => dispatch(userActions.showPostAdvancedSettings({ formId })), setPayoutType: (formId, payoutType) => dispatch( @@ -1435,14 +1467,17 @@ export default (formId) => successCallback, errorCallback, startLoadingIndicator, + hostConfig, useHive, selectedCoverImage, }) => { + const appDomain = hostConfig['APP_DOMAIN']; + const preferHive = hostConfig['PREFER_HIVE']; const isEdit = type === 'edit'; const isNew = /^submit_/.test(type); if (isNew && !author) { - useHive = PREFER_HIVE; + useHive = hostConfig['PREFER_HIVE']; } // Wire up the current and parent props for either an Edit or a Submit (new post) @@ -1456,9 +1491,9 @@ export default (formId) => // permlink, assigned in TransactionSaga } : // edit existing - isEdit - ? { author, permlink, parent_author, parent_permlink } - : null; + isEdit + ? { author, permlink, parent_author, parent_permlink } + : null; if (!linkProps) throw new Error('Unknown type: ' + type); @@ -1473,10 +1508,14 @@ export default (formId) => let rtags; { const html = isHtml ? body : remarkable.render(body); - rtags = HtmlReady(html, { mutate: false }); + rtags = HtmlReady(html, { + mutate: false, + appDomain, + useHive: preferHive, + }); } - allowedTags.forEach((tag) => { + allowedTags.forEach(tag => { rtags.htmltags.delete(tag); }); if (isHtml) rtags.htmltags.delete('html'); // html tag allowed only in HTML mode @@ -1484,7 +1523,7 @@ export default (formId) => errorCallback( 'Please remove the following HTML elements from your post: ' + Array(...rtags.htmltags) - .map((tag) => `<${tag}>`) + .map(tag => `<${tag}>`) .join(', ') ); return; @@ -1493,7 +1532,8 @@ export default (formId) => const metaTags = allTags( tags, originalPost.category, - rtags.hashtags + rtags.hashtags, + hostConfig ); // merge @@ -1533,19 +1573,19 @@ export default (formId) => delete meta.links; } - meta.app = `${APP_NAME.toLowerCase()}/0.1`; + meta.app = `${hostConfig['APP_NAME'].toLowerCase()}/0.1`; if (isStory) { meta.format = isHtml ? 'html' : 'markdown'; } const sanitizeErrors = []; - sanitize(body, sanitizeConfig({ sanitizeErrors })); + sanitize(body, sanitizeConfig({ appDomain, sanitizeErrors })); if (sanitizeErrors.length) { errorCallback(sanitizeErrors.join('. ')); return; } - if (meta.tags && meta.tags.length > MAX_TAG) { + if (meta.tags && meta.tags.length > hostConfig['APP_MAX_TAG']) { const includingCategory = isEdit ? tt('reply_editor.including_the_category', { rootCategory: originalPost.category, @@ -1555,7 +1595,7 @@ export default (formId) => tt('reply_editor.use_limited_amount_of_tags', { tagsLength: meta.tags.length, includingCategory, - maxTags: MAX_TAG, + maxTags: hostConfig['APP_MAX_TAG'], }) ); return; @@ -1568,12 +1608,14 @@ export default (formId) => originalBody, comment_options: isEdit ? null : {}, }; + const debtSymbol = useHive ? 'HBD' : 'SBD'; // Avoid changing payout option during edits #735 if (!isEdit) { switch (payoutType) { case '0%': // decline payout - __config.comment_options.max_accepted_payout = - '0.000 HBD'; + __config.comment_options.max_accepted_payout = `0.000 ${ + debtSymbol + }`; break; case '100%': // 100% steem power payout __config.comment_options.percent_steem_dollars = 0; // 10000 === 100% (of 50%) @@ -1581,16 +1623,18 @@ export default (formId) => default: // 50% steem power, 50% sd+steem } if ( - SCOT_DEFAULT_BENEFICIARY_ACCOUNT && + hostConfig['SCOT_DEFAULT_BENEFICIARY_ACCOUNT'] && beneficiaries.filter( elt => elt.username === - SCOT_DEFAULT_BENEFICIARY_ACCOUNT + hostConfig['SCOT_DEFAULT_BENEFICIARY_ACCOUNT'] ).length == 0 ) { beneficiaries.push({ - username: SCOT_DEFAULT_BENEFICIARY_ACCOUNT, - percent: SCOT_DEFAULT_BENEFICIARY_PERCENT, + username: + hostConfig['SCOT_DEFAULT_BENEFICIARY_ACCOUNT'], + percent: + hostConfig['SCOT_DEFAULT_BENEFICIARY_PERCENT'], }); } if (beneficiaries && beneficiaries.length > 0) { @@ -1599,16 +1643,19 @@ export default (formId) => 0, { beneficiaries: beneficiaries - .sort((a, b) => - a.username < b.username - ? -1 - : a.username > b.username - ? 1 - : 0 + .sort( + (a, b) => + a.username < b.username + ? -1 + : a.username > b.username + ? 1 + : 0 ) - .map((elt) => ({ + .map(elt => ({ account: elt.username, - weight: parseInt(elt.percent) * 100, + weight: Math.round( + parseFloat(elt.percent) * 100 + ), })), }, ], @@ -1617,7 +1664,7 @@ export default (formId) => if (maxAcceptedPayout !== null && maxAcceptedPayout !== 0) { __config.comment_options.max_accepted_payout = `${maxAcceptedPayout.toFixed( 3 - )} HBD`; + )} ${debtSymbol}`; } } @@ -1625,9 +1672,8 @@ export default (formId) => ...linkProps, category: originalPost.category || - (COMMUNITY_CATEGORY - ? COMMUNITY_CATEGORY - : metaTags.first()), + hostConfig['COMMUNITY_CATEGORY'] || + metaTags.first(), title, body, json_metadata: JSON.stringify(meta), diff --git a/src/app/components/elements/ReviveAd.jsx b/src/app/components/elements/ReviveAd.jsx index 2bfc8668f..e0d881dad 100644 --- a/src/app/components/elements/ReviveAd.jsx +++ b/src/app/components/elements/ReviveAd.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { REVIVE_ADS, NO_ADS_STAKE_THRESHOLD } from 'app/client_config'; +import { Map } from 'immutable'; class ReviveAd extends React.Component { componentDidMount() { @@ -25,6 +25,13 @@ export default connect((state, ownProps) => { const tokenBalances = current_account ? current_account.get('token_balances') : null; + const noAdsStakeThreshold = state.app.getIn([ + 'hostConfig', + 'NO_ADS_STAKE_THRESHOLD', + ]); + const reviveAdsConfig = state.app + .getIn(['hostConfig', 'REVIVE_ADS'], Map()) + .toJS(); // Do not show if server side. let showAd = Boolean(process.env.BROWSER); @@ -33,7 +40,7 @@ export default connect((state, ownProps) => { const delegatedStake = tokenBalancesJs.delegationsOut || '0'; const stakeBalance = parseFloat(tokenBalancesJs.stake) + parseFloat(delegatedStake); - if (stakeBalance >= NO_ADS_STAKE_THRESHOLD) { + if (stakeBalance >= noAdsStakeThreshold) { showAd = false; } } @@ -41,9 +48,9 @@ export default connect((state, ownProps) => { const adKey = ownProps.adKey; let zoneId = ''; let reviveId = ''; - if (REVIVE_ADS[adKey]) { - zoneId = REVIVE_ADS[adKey].zoneId; - reviveId = REVIVE_ADS[adKey].reviveId; + if (reviveAdsConfig[adKey]) { + zoneId = reviveAdsConfig[adKey].zoneId; + reviveId = reviveAdsConfig[adKey].reviveId; } else { showAd = false; } diff --git a/src/app/components/elements/SearchInput/styles.scss b/src/app/components/elements/SearchInput/styles.scss index 030e14807..4c5633b31 100644 --- a/src/app/components/elements/SearchInput/styles.scss +++ b/src/app/components/elements/SearchInput/styles.scss @@ -31,15 +31,18 @@ form.search-input { color: transparent } &:hover { - background-color: $color-teal; + @include themify($themes) { + background-color: themed('colorAccent'); + } } &:focus { width: 180px; @include themify($themes) { border: themed('moduleBackgroundColor'); + border-color: themed('colorAccent'); } - border-color: $color-teal; // box-shadow: 0 0 5px rgba(109,207,246,.5); + width: 180px; padding-left: 2.5rem; @include themify($themes) { color: themed('textColorPrimary'); @@ -92,8 +95,9 @@ form.search-input--expanded { color: transparent; font-family: $font-primary; width: 100%; - - border-color: $color-teal; + @include themify($themes) { + border-color: themed('colorAccent'); + } // box-shadow: 0 0 5px rgba(109,207,246,.5); padding-left: 2.5rem; cursor: auto; @@ -103,10 +107,6 @@ form.search-input--expanded { @include themify($themes) { color: themed('textColorPrimary'); } - &:hover { - // background-color: $color-teal; - // color: $color-white; - } &::placeholder { @include themify($themes) { color: themed('textColorSecondary'); diff --git a/src/app/components/elements/SidebarLinks.jsx b/src/app/components/elements/SidebarLinks.jsx index f187750df..b58239266 100644 --- a/src/app/components/elements/SidebarLinks.jsx +++ b/src/app/components/elements/SidebarLinks.jsx @@ -2,7 +2,7 @@ import React from 'react'; import tt from 'counterpart'; import { Link } from 'react-router'; -const SidebarLinks = ({ username, topics }) => ( +const SidebarLinks = ({ username, scotTokenSymbol, topics }) => (

{tt('g.links')}

@@ -27,6 +27,26 @@ const SidebarLinks = ({ username, topics }) => ( {tt('g.my_wallet')} + {scotTokenSymbol === 'LAGO' && ( +
  • + + LagoTube + +
  • + )} + {scotTokenSymbol === 'WEED' && ( +
  • + + WeedCash DTube + +
  • + )}
    diff --git a/src/app/components/elements/SidebarNewUsers.jsx b/src/app/components/elements/SidebarNewUsers.jsx index 33cd30f29..73de0be6b 100644 --- a/src/app/components/elements/SidebarNewUsers.jsx +++ b/src/app/components/elements/SidebarNewUsers.jsx @@ -1,7 +1,7 @@ import React from 'react'; import tt from 'counterpart'; import { connect } from 'react-redux'; -import { SIGNUP_URL } from 'shared/constants'; +import { HIVE_SIGNUP_URL, SIGNUP_URL } from 'shared/constants'; import Icon from 'app/components/elements/Icon'; import { Link } from 'react-router'; diff --git a/src/app/components/elements/SidebarToken.jsx b/src/app/components/elements/SidebarToken.jsx index 301fcf6fd..388fd1257 100644 --- a/src/app/components/elements/SidebarToken.jsx +++ b/src/app/components/elements/SidebarToken.jsx @@ -1,10 +1,121 @@ -import React from 'react'; +import React, { Component } from 'react'; import tt from 'counterpart'; +import { Link } from 'react-router'; +import axios from 'axios'; import { formatDecimal, parsePayoutAmount, } from 'app/utils/ParsersAndFormatters'; +class Livefeed extends Component { + state = { + buidlprice: 'buidl', + hive: 'hive', + bitcoin: 'bitcoin', + }; + + componentDidMount() { + fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=hive&vs_currencies=usd' + ) + .then(response => response.json()) + .then(data => { + this.setState({ + hive: data.hive.usd.toLocaleString( + 'en-US' + ) + }); + }); + fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd' + ) + .then(response => response.json()) + .then(data => { + this.setState({ + bitcoin: data.bitcoin.usd.toLocaleString( + 'en-US' + ) + }); + }); + } + + render() { + axios + .request({ + method: 'POST', + url: 'https://ha.herpc.dtools.dev/contracts', + headers: { 'Content-Type': 'application/json' }, + data: { + jsonrpc: '2.0', + id: 1, + method: 'findOne', + params: { + contract: 'market', + table: 'metrics', + query: { symbol: 'BUIDL' }, + offset: 0, + limit: 1000, + }, + }, + }) + .then(response => { + let buidlToken = response.data.result.lastPrice.toLocaleString( + 'en-US' + ); + this.setState({ + buidlprice: buidlToken, + }); + }) + .catch(function(error) { + console.error(error); + }); + const buidl = this.state.buidlprice; + const btc = this.state.bitcoin; + const hive =this.state.hive; + return ( +
    +
    +
    + Live Price Feed +
    +
    +
    + {' '} + {btc} USD +
    +

    + Bitcoin Market Value by{' '} + + @Coingecko + +

    +
    +
    + {hive} USD +
    +

    + Hive Market Value by{' '} + + @Coingecko + +

    +
    +
    + {' '} + {buidl} USD +
    +

    + Buidl Market Value by{' '} + + @Hive-Engine + +

    +
    +
    + ); + } +} + const SidebarToken = ({ scotToken, scotTokenCirculating, @@ -39,89 +150,18 @@ const SidebarToken = ({ ); return ( +
    -
    - -
    -
    -
      -
    • -
      -
      {tt('g.total')}
      -
      - {total[0]} - {total[1]} -
      -
      -
    • -
    • -
      -
      - {tt('g.circulating')} ( - - {circulatingRate[0]} - - - {circulatingRate[1]} - - %) -
      -
      - - {circulating[0]} - - - {circulating[1]} - -
      -
      -
    • -
    • -
      -
      - {tt('g.burn')} ( - {burnRate[0]} - {burnRate[1]} - %) -
      -
      - {burn[0]} - {burn[1]} -
      -
      -
    • -
    • -
      -
      - {tt('g.staking')} ( - - {stakingRate[0]} - - - {stakingRate[1]} - - %) -
      -
      - {staking[0]} - {staking[1]} -
      -
      -
    • -
    -
    +
    + Explore Build-it ? +
    +
    +

    + + Frequently Asked Questions. +

    +
    +
    ); }; diff --git a/src/app/components/elements/SortOrder/styles.scss b/src/app/components/elements/SortOrder/styles.scss index 36e571b8f..5f801a74d 100644 --- a/src/app/components/elements/SortOrder/styles.scss +++ b/src/app/components/elements/SortOrder/styles.scss @@ -15,15 +15,19 @@ li.nav__block-list-item { font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; } &--active { - box-shadow: 0px 5px 0px -2px $color-teal; + @include themify($themes) { + box-shadow: 0px 5px 0px -2px themed('colorAccent'); a { - color: $color-teal; + color: themed('colorAccent'); } + } } &:hover, &:focus { - box-shadow: 0px 5px 0px -2px $color-teal; + @include themify($themes) { + box-shadow: 0px 5px 0px -2px themed('colorAccent'); a { - color: $color-teal; + color: themed('colorAccent'); } + } } -} \ No newline at end of file +} diff --git a/src/app/components/elements/TagList.jsx b/src/app/components/elements/TagList.jsx index 0567c8f1d..581521496 100644 --- a/src/app/components/elements/TagList.jsx +++ b/src/app/components/elements/TagList.jsx @@ -1,24 +1,23 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router'; -import { COMMUNITY_CATEGORY, APP_NAME } from 'app/client_config'; -function getDisplayTag(tag) { - return tag === COMMUNITY_CATEGORY ? APP_NAME : ` #${tag} `; +function getDisplayTag(tag, hiveTag, appName) { + return tag === hiveTag ? appName : ` #${tag} `; } import { normalizeTags } from 'app/utils/StateFunctions'; class TagList extends Component { render() { - const { post } = this.props; + const { post, hiveTag, appName } = this.props; const category = post.get('category'); const link = tag => { if (tag == category) return null; return ( - {getDisplayTag(tag)} + {getDisplayTag(tag, hiveTag, appName)} ); }; @@ -35,5 +34,7 @@ export default connect( // mapStateToProps (state, ownProps) => ({ post: ownProps.post, + hiveTag: ownProps.hiveTag, + appName: ownProps.appName, }) )(TagList); diff --git a/src/app/components/elements/Userpic.jsx b/src/app/components/elements/Userpic.jsx index c46cad562..44b58abcc 100644 --- a/src/app/components/elements/Userpic.jsx +++ b/src/app/components/elements/Userpic.jsx @@ -14,7 +14,7 @@ class Userpic extends Component { if (this.props.hide) return null; const { account, size } = this.props; - const url = imageProxy() + `u/${account}/avatar${size}`; + const url = imageProxy(this.props.hive) + `u/${account}/avatar${size}`; const style = { backgroundImage: `url(${url})` }; return
    ; } @@ -25,7 +25,7 @@ Userpic.propTypes = { }; export default connect((state, ownProps) => { - const { account, size, hideIfDefault } = ownProps; + const { account, hive, size, hideIfDefault } = ownProps; let hide = false; if (hideIfDefault) { @@ -40,5 +40,6 @@ export default connect((state, ownProps) => { account: account == 'steemitblog' ? 'steemitdev' : account, size: size && sizeList.indexOf(size) > -1 ? '/' + size : '', hide, + hive, }; })(Userpic); diff --git a/src/app/components/elements/Voting.jsx b/src/app/components/elements/Voting.jsx index f472b59b9..e254a9173 100644 --- a/src/app/components/elements/Voting.jsx +++ b/src/app/components/elements/Voting.jsx @@ -1,4 +1,4 @@ -import { List } from 'immutable'; +import { List, Map } from 'immutable'; import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; @@ -7,7 +7,6 @@ import tt from 'counterpart'; import CloseButton from 'app/components/elements/CloseButton'; import * as transactionActions from 'app/redux/TransactionReducer'; import Icon from 'app/components/elements/Icon'; -import { LIQUID_TOKEN_UPPERCASE, SCOT_DENOM } from 'app/client_config'; import FormattedAsset from 'app/components/elements/FormattedAsset'; import { pricePerSteem } from 'app/utils/StateFunctions'; import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; @@ -19,6 +18,7 @@ import { getDate } from 'app/utils/Date'; import DropdownMenu from 'app/components/elements/DropdownMenu'; import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper'; import Dropdown from 'app/components/elements/Dropdown'; +import axios from 'axios'; const ABOUT_FLAG = (
    @@ -39,18 +39,6 @@ const MAX_VOTES_DISPLAY = 20; const MAX_WEIGHT = 10000; const MIN_PAYOUT = 0.02; -function amt(string_amount) { - return parsePayoutAmount(string_amount); -} - -function fmt(decimal_amount, asset = null) { - return formatDecimal(decimal_amount).join('') + (asset ? ' ' + asset : ''); -} - -function abs(value) { - return Math.abs(parseInt(value)); -} - class Voting extends React.Component { static propTypes = { // HTML properties @@ -67,6 +55,7 @@ class Voting extends React.Component { enable_slider: PropTypes.bool, voting: PropTypes.bool, scotData: PropTypes.object, + downvoteEnabled: PropTypes.bool, }; static defaultProps = { @@ -77,6 +66,7 @@ class Voting extends React.Component { super(props); this.state = { showWeight: false, + buidlprice: 0, sliderWeight: { up: MAX_WEIGHT, down: MAX_WEIGHT, @@ -227,13 +217,44 @@ class Voting extends React.Component { voteRegenSec, downvoteRegenSec, rewardData, + hostConfig, tokenBeneficiary, + downvoteEnabled, useHive, } = this.props; + axios + .request({ + method: 'POST', + url: 'https://ha.herpc.dtools.dev/contracts', + headers: { 'Content-Type': 'application/json' }, + data: { + jsonrpc: '2.0', + id: 1, + method: 'findOne', + params: { + contract: 'market', + table: 'metrics', + query: { symbol: 'BUIDL' }, + offset: 0, + limit: 1000, + }, + }, + }) + .then(response => { + let buidlToken = response.data.result.lastPrice.toLocaleString( + 'en-US' + ); + this.setState({ + buidlprice: buidlToken, + }); + }) + .catch(function(error) { + console.error(error); + }); + const { votingUp, votingDown, showWeight, showWeightDir } = this.state; - const scotDenom = Math.pow(10, scotPrecision); // Incorporate regeneration time. const currentVp = votingData ? Math.min( @@ -282,37 +303,30 @@ class Voting extends React.Component { rewardData.pending_rshares; let rsharesTotal = 0; - + if (scotData) { - rsharesTotal = scotData.get('vote_rshares'); + //abod new tag + rsharesTotal = parseFloat(scotData.get('vote_rshares') * this.state.buidlprice); scot_pending_token = applyRewardsCurve(rsharesTotal); - scot_total_curator_payout = parseInt( - scotData.get('curator_payout_value') + scot_total_curator_payout = parseFloat( + scotData.get('curator_payout_value') * this.state.buidlprice ); - scot_total_author_payout = parseInt( - scotData.get('total_payout_value') + scot_total_author_payout = parseFloat( + scotData.get('total_payout_value') * this.state.buidlprice ); - scot_token_bene_payout = parseInt( - scotData.get('beneficiaries_payout_value') + scot_token_bene_payout = parseFloat( + scotData.get('beneficiaries_payout_value') * this.state.buidlprice ); - promoted = parseInt(scotData.get('promoted')); + promoted = parseFloat(scotData.get('promoted') * this.state.buidlprice); decline_payout = scotData.get('decline_payout'); scot_total_author_payout -= scot_total_curator_payout; scot_total_author_payout -= scot_token_bene_payout; payout = cashout_active ? scot_pending_token : scot_total_author_payout + scot_total_curator_payout; - - // divide by scotDenom - scot_pending_token /= scotDenom; - scot_total_curator_payout /= scotDenom; - scot_total_author_payout /= scotDenom; - scot_token_bene_payout /= scotDenom; - payout /= scotDenom; - promoted /= scotDenom; } - const total_votes = post.getIn(['stats', 'total_votes']); + const total_votes = active_votes ? active_votes.size : 0; if (payout < 0.0) payout = 0.0; const votingUpActive = voting && votingUp; @@ -340,7 +354,7 @@ class Voting extends React.Component { (up ? currentVp : currentDownvotePower) / (10000 * 100); const newValue = applyRewardsCurve(rsharesTotal + rshares); - valueEst = (newValue / scotDenom - scot_pending_token).toFixed( + valueEst = (newValue - scot_pending_token ).toFixed( scotPrecision ); } @@ -384,12 +398,14 @@ class Voting extends React.Component { }; let downVote; - if (true) { + if (downvoteEnabled) { const down = ( + + ); const classDown = 'Voting__button Voting__button-down' + @@ -473,10 +489,20 @@ class Voting extends React.Component { } const up = ( + + + ); + const ups = ( + + + ); const classUp = 'Voting__button Voting__button-up' + @@ -487,7 +513,7 @@ class Voting extends React.Component { if (promoted > 0) { payoutItems.push({ value: `Promotion Cost ${promoted.toFixed(scotPrecision)} ${ - LIQUID_TOKEN_UPPERCASE + hostConfig['LIQUID_TOKEN_UPPERCASE'] }`, }); } @@ -496,9 +522,7 @@ class Voting extends React.Component { } else if (cashout_active) { payoutItems.push({ value: 'Pending Payout' }); payoutItems.push({ - value: `${scot_pending_token.toFixed(scotPrecision)} ${ - LIQUID_TOKEN_UPPERCASE - }`, + value: `$${scot_pending_token.toFixed(scotPrecision)}`, }); payoutItems.push({ value: , @@ -507,26 +531,20 @@ class Voting extends React.Component { // - payout instead of total_author_payout: total_author_payout can be zero with 100% beneficiary // - !cashout_active is needed to avoid the info is also shown for pending posts. payoutItems.push({ - value: `Past Token Payouts ${payout.toFixed(scotPrecision)} ${ - LIQUID_TOKEN_UPPERCASE - }`, + value: `Past Token Payouts $${payout.toFixed(8)}`, }); payoutItems.push({ - value: `- Author ${scot_total_author_payout.toFixed( - scotPrecision - )} ${LIQUID_TOKEN_UPPERCASE}`, + value: `- Author $${scot_total_author_payout.toFixed(8)}`, }); payoutItems.push({ - value: `- Curator ${scot_total_curator_payout.toFixed( - scotPrecision - )} ${LIQUID_TOKEN_UPPERCASE}`, + value: `- Curator $${scot_total_curator_payout.toFixed(8)}`, }); // Uncomment to enable if (false && scot_token_bene_payout > 0 && tokenBeneficiary) { payoutItems.push({ value: `- Token Benefactor ${scot_token_bene_payout.toFixed( scotPrecision - )} ${LIQUID_TOKEN_UPPERCASE}`, + )} ${'LIQUID_TOKEN_UPPERCASE'}`, }); } } @@ -574,7 +592,8 @@ class Voting extends React.Component { {payoutItems.length > 0 && } @@ -602,16 +621,16 @@ class Voting extends React.Component { const denom = rsharesTotal > 0 ? applyRewardsCurve(rsharesTotal) - : scotDenom; + : 1; for (let i = 0; i < avotes.length; i++) { const vote = avotes[i]; vote.estimate = ( pot * - (applyRewardsCurve(currRshares + vote.rshares) - + (applyRewardsCurve(currRshares + parseFloat(vote.rshares)) - applyRewardsCurve(currRshares)) / denom ).toFixed(scotPrecision); - currRshares += vote.rshares; + currRshares += parseFloat(vote.rshares); } } @@ -630,7 +649,8 @@ class Voting extends React.Component { ) { const { percent, voter, estimate } = avotes[v]; const sign = Math.sign(percent); - const estimateStr = estimate ? ` (${estimate})` : ''; + const conEstimate = estimate * this.state.buidlprice; + const estimateStr = '$'+conEstimate.toFixed(8) ? ` (${'$'+conEstimate.toFixed(8)})` : ''; if (sign === 0) continue; voters.push({ value: (sign > 0 ? '+ ' : '- ') + voter + estimateStr, @@ -739,6 +759,7 @@ class Voting extends React.Component { export default connect( // mapStateToProps (state, ownProps) => { + const hostConfig = state.app.get('hostConfig', Map()).toJS(); const post = ownProps.post || state.global.getIn(['content', ownProps.post_ref]); @@ -747,7 +768,11 @@ export default connect( throw 'post not found'; } const scotConfig = state.app.get('scotConfig'); - const scotData = post.getIn(['scotData', LIQUID_TOKEN_UPPERCASE]); + //token itself + const scotData = post.getIn([ + 'scotData', + hostConfig['LIQUID_TOKEN_UPPERCASE'], + ]); const commentPool = post.get('parent_author') && scotConfig.getIn(['info', 'enable_comment_reward_pool'], false); @@ -795,7 +820,7 @@ export default connect( ? current_account.get('username') : null; const votingData = current_account - ? current_account.get(useHive ? 'hive_voting' : 'voting') + ? current_account.get('voting') : null; const voting = state.global.get( `transaction_vote_active_${author}_${permlink}` @@ -825,10 +850,7 @@ export default connect( voting, votingData, scotData, - scotPrecision: scotConfig.getIn( - ['info', 'precision'], - Math.log10(SCOT_DENOM) - ), + scotPrecision: scotConfig.getIn(['info', 'precision']), voteRegenSec: scotConfig.getIn( ['config', 'vote_regeneration_seconds'], 5 * 24 * 60 * 60 @@ -838,10 +860,15 @@ export default connect( 5 * 24 * 60 * 60 ), rewardData, + hostConfig, tokenBeneficiary: scotConfig.getIn( ['config', 'beneficiaries_account'], '' ), + downvoteEnabled: !scotConfig.getIn( + ['config', 'disable_downvoting'], + false + ), useHive, }; }, diff --git a/src/app/components/elements/Voting.scss b/src/app/components/elements/Voting.scss index b0ecdfa5f..34f02c46a 100644 --- a/src/app/components/elements/Voting.scss +++ b/src/app/components/elements/Voting.scss @@ -1,428 +1,444 @@ .Voting { - white-space: nowrap !important; - span { white-space: nowrap !important; - } - .dropdown-pane { - @include themify($themes) { - border: themed('borderDark'); + span { + white-space: nowrap !important; } - } -} - -.Voting__button { - .Icon { - // Put the icon in a layer to improve rendering performance (scrolling especially) - transform: translate3d(0,0,0); - } - path { - fill: $dark-gray; - } - circle { - stroke: $dark-gray; - } - > a:hover path { - fill: $black; - } -} - -.Voting__about-flag { - margin-right: 30px; - .button { - text-decoration: none; - font-weight: bold; - transition: 0.2s all ease-in-out; - text-transform: capitalize; - border-radius: 0; - @include font-size(18px); - @include themify($themes) { - background-color: themed('buttonBackground'); - box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 5px 5px 0 0 themed('buttonBoxShadow'); - color: themed('buttonText'); - } - &:hover, &:focus { - @include themify($themes) { - background-color: themed('buttonBackgroundHover'); - box-shadow: 2px 2px 2px 0 rgba(0,0,0,0.1), 7px 7px 0 0 themed('buttonBoxShadowHover'); - color: themed('buttonTextHover'); - text-shadow: 0 1px 0 rgba(0,0,0,0.20); - } - } - &:visited, &:active { + .dropdown-pane { @include themify($themes) { - color: themed('buttonText'); + border: themed('borderDark'); } } } -} -.Voting__button-up { - .Icon { - margin-left: 0; - border-radius: 50%; - } - div > a { - padding-right: 0.5rem; - } - > a { - padding-right: 0.4rem; - } - path { - @include themify($themes) { - fill: themed('textColorAccent'); + .Voting__button { + .Icon { + // Put the icon in a layer to improve rendering performance (scrolling especially) + transform: translate3d(0,0,0); + } + path { + fill: $dark-gray; + } + circle { + stroke: $dark-gray; + } + > a:hover path { + fill: $black; } } - circle { + + .Voting__about-flag { + margin-right: 30px; + .button { + text-decoration: none; + font-weight: bold; + transition: 0.2s all ease-in-out; + text-transform: capitalize; + border-radius: 0; + @include font-size(18px); @include themify($themes) { - stroke: themed('textColorAccent'); + background-color: themed('buttonBackground'); + box-shadow: 0px 0px 0px 0 rgba(0,0,0,0), 5px 5px 0 0 themed('buttonBoxShadow'); + color: themed('buttonText'); } - } - // exclude small devices - @media screen and (min-width: 39.9375em) { - .Icon:hover { - box-shadow: 0 0 0 rgba(75, 162, 242, 1); - animation: pulse 2s infinite; + &:hover, &:focus { + @include themify($themes) { + background-color: themed('buttonBackgroundHover'); + box-shadow: 2px 2px 2px 0 rgba(0,0,0,0.1), 7px 7px 0 0 themed('buttonBoxShadowHover'); + color: themed('buttonTextHover'); + text-shadow: 0 1px 0 rgba(0,0,0,0.20); + } } + &:visited, &:active { + @include themify($themes) { + color: themed('buttonText'); + } + } + } } - .close-button { - position: relative; - margin-left: 1em; - } - > .dropdown-comp > .dropdown__content { - position: absolute; - top: -30px; - left: -26px; - } -} -.Voting__button-up > a:hover, a.confirm_weight:hover { + .Voting__button-up { + .Icon { + margin-left: 0; + border-radius: 50%; + } + div > a { + padding-right: 0.5rem; + } + > a { + padding-right: 0.4rem; + } path { - fill: #fff; + @include themify($themes) { + fill: themed('textColorAccent'); + } } circle { @include themify($themes) { - fill: themed('colorAccent'); - stroke: themed('colorAccent'); + stroke: themed('textColorAccent'); + } + } + // exclude small devices + @media screen and (min-width: 39.9375em) { + .Icon:hover { + box-shadow: 0 0 0 rgba(75, 162, 242, 1); + animation: 1.5s ease 0s infinite beat; } + } + .close-button { + position: relative; + margin-left: 1em; + } + > .dropdown-comp > .dropdown__content { + position: absolute; + top: -30px; + left: -26px; } + } + + .Voting__button-up > a:hover, a.confirm_weight:hover { + path { + fill: #fff; + } + circle { + @include themify($themes) { + fill: themed('colorAccent'); + stroke: themed('colorAccent'); + } + } + svg { + background-size: contain; + border-radius: 50%; + } + } + + .Voting__button-up.votingUp { + padding-right: 0.5rem; svg { - background-size: contain; - border-radius: 50%; + @include themify($themes) { + border: 1px solid themed('colorAccent'); + border-right-color: transparent; + border-top-color: transparent; + } + border-radius: 50%; + animation: loading 500ms infinite linear; } -} + } -.Voting__button-up.votingUp { - padding-right: 0.5rem; - svg { - border: 1px solid $color-teal; + .Voting__button-down.votingDown svg { + border: 1px solid $color-red; border-radius: 50%; border-right-color: transparent; border-top-color: transparent; animation: loading 500ms infinite linear; } -} - -.Voting__button-down.votingDown svg { - border: 1px solid $color-red; - border-radius: 50%; - border-right-color: transparent; - border-top-color: transparent; - animation: loading 500ms infinite linear; -} - -.Voting__button-down { - margin-right: 0.5rem; - .Voting__button-downvotes { - color: #aaa; - font-size: 80%; - vertical-align: 15%; - padding-right: 0.1rem; - cursor: default; - } - a path { - fill: #000; - } - a circle { - stroke: #000; - } - a:hover circle { - fill: #555; - } - a:hover path { - fill: #fff; - } + .Voting__button-down { + margin-right: 0.5rem; + .Voting__button-downvotes { + color: #aaa; + font-size: 80%; + vertical-align: 15%; + padding-right: 0.1rem; + cursor: default; + } - &.Voting__button--downvoted { a path { - fill: #fff; + transition: fill 0.5s ease-out 1ms; + fill: transparent; } a circle { - fill: #f99; + stroke: transparent; } a:hover circle { - fill: #f66; + fill: #555; + } + a:hover path { + fill: rgb(245, 64, 64); } - } -} -.Voting__button--upvoted { - .Icon:hover { - animation: none !important; - } - path { - fill: #fff !important; + &.Voting__button--downvoted { + a path { + fill: red; + stroke: red; + } + a circle { + fill: #f99; + stroke: #f99; + } + a:hover circle { + fill: #f66; + stroke: #f66; + } + } } -} -.Voting__button--upvoted circle { - @include themify($themes) { - fill: themed('colorAccent'); - stroke: themed('colorAccent'); + .Voting__button--upvoted { + .Icon:hover { + animation: none !important; } -} - -.Voting__button--upvoted a:hover path { - fill: #fff !important; -} - -.Voting__button--upvoted a:hover circle { - @include themify($themes) { - fill: themed('colorAccentHover'); - stroke: themed('colorAccentHover'); + path { + fill: #4fa5fc !important; } -} + } -.Voting__button--downvoted path { - fill: $color-red; -} + .Voting__button--upvoted circle { + @include themify($themes) { + fill: themed('colorAccent'); + stroke: themed('colorAccent'); + } + } + .Voting__button--upvoted a:hover path { + fill: rgb(79, 165, 252, 0.5) !important; + } -.PostFull .Voting .DropdownMenu.Voting__voters_list { - /* reserve enough space for dropdown (otherwise overflow) */ - min-width: 140px; -} + .Voting__button--upvoted a:hover circle { + @include themify($themes) { + fill: themed('colorAccentHover'); + stroke: themed('colorAccentHover'); + } + } -.Voting .DropdownMenu.Voting__pane ul { - width: auto; - min-width: 260px; -} + .Voting__button--downvoted path { + fill: $color-red; + } -.Voting .DropdownMenu ul { - width: auto; - min-width: 140px; - max-width: 360px; - li > a { - padding: 0 0.5rem; - line-height: 1.25; + .PostFull .Voting .DropdownMenu.Voting__voters_list { + /* reserve enough space for dropdown (otherwise overflow) */ + min-width: 140px; } - li > span { - padding: 0 0.5rem; - line-height: 1.25; + .Voting .DropdownMenu.Voting__pane ul { + width: auto; + min-width: 260px; } -} -.DropdownMenu.Voting__voters_list ul { - min-width: 140px; + .Voting .DropdownMenu ul { + width: auto; + min-width: 140px; + max-width: 360px; - li > span { - padding: 0.25rem 0.5rem; - font-size: 82.5%; - display: block; - @include themify($themes) { - color: themed('textColorSecondary'); + li > a { + padding: 0 0.5rem; + line-height: 1.25; } - } -} -.Voting__inner { - @include themify($themes) { - border-right: themed('borderLight'); - } - padding-right: .8rem; - margin-right: .6rem; - .DropdownMenu .Icon.dropdown-arrow { - margin-right: -0.5rem; - position: relative; - top: 2px; - } - .cancel { - font-size: 80%; - padding: 0 0.4em; - margin-left: 0.5rem; - margin-right: 2px; - background-color: #f8f8f8; - border-radius: 50%; - border: 1px solid #dadada; - color: #8a8a8a; + li > span { + padding: 0 0.5rem; + line-height: 1.25; + } } -} -.Voting__adjust_weight_down { - @extend .Voting__adjust_weight; + .DropdownMenu.Voting__voters_list ul { + min-width: 140px; + + li > span { + padding: 0.25rem 0.5rem; + font-size: 82.5%; + display: block; + @include themify($themes) { + color: themed('textColorSecondary'); + } + } + } + .Voting__inner { @include themify($themes) { - background-color: themed('backgroundColor'); + border-right: themed('borderLight'); + } + padding-right: .8rem; + margin-right: .6rem; + .DropdownMenu .Icon.dropdown-arrow { + margin-right: -0.5rem; + position: relative; + top: 2px; } + .cancel { + font-size: 80%; + padding: 0 0.4em; + margin-left: 0.5rem; + margin-right: 2px; + background-color: #f8f8f8; + border-radius: 50%; + border: 1px solid #dadada; + color: #8a8a8a; + } + } - right: 0; - width: auto !important; - max-width: 500px; + .Voting__adjust_weight_down { + @extend .Voting__adjust_weight; - div.clear { - clear: both; - } - p, span { - white-space: normal !important; + @include themify($themes) { + background-color: themed('backgroundColor'); + } + + right: 0; + width: auto !important; + max-width: 500px; + + div.clear { + clear: both; + } + p, span { + white-space: normal !important; + } + .weight-display { + color: $color-red !important; + } + .rangeslider { + margin-right: 2rem; + } + } + + .Voting__adjust_weight { + padding: 20px 20px 20px; + margin-right: 10px; + width: 350px; + min-width: 320px; + overflow: hidden; + .Icon:hover { + animation: none !important; } .weight-display { - color: $color-red !important; + width: 3rem; + float: left; + margin-right: 0.5rem; + text-align: right; + color: $dark-gray; + line-height: 2.6rem; + } + .voting-power-display { + float: left; + margin-right: 0.5rem; + text-align: right; + color: $dark-gray; + line-height: 2.6rem; + } + .voting-est-display { + float: left; + margin-right: 0.5rem; + text-align: right; + color: $dark-gray; + line-height: 2.6rem; + } + a.confirm_weight { + float: left; + width: 2rem; + height: 2rem; + line-height: 2.6rem; } .rangeslider { - margin-right: 2rem; - } -} - -.Voting__adjust_weight { - padding: 20px 20px 20px; - margin-right: 10px; - width: 350px; - min-width: 320px; - overflow: hidden; - .Icon:hover { - animation: none !important; - } - .weight-display { - width: 3rem; - float: left; - margin-right: 0.5rem; - text-align: right; - color: $dark-gray; - line-height: 2.6rem; - } - .voting-power-display { - float: left; - margin-right: 0.5rem; - text-align: right; - color: $dark-gray; - line-height: 2.6rem; - } - .voting-est-display { - float: left; - margin-right: 0.5rem; - text-align: right; - color: $dark-gray; - line-height: 2.6rem; - } - a.confirm_weight { - float: left; - width: 2rem; - height: 2rem; - line-height: 2.6rem; - } - .rangeslider { - position: relative; - float: left; - background: #e6e6e6; - .rangeslider__fill, .rangeslider__handle { - position: absolute; + position: relative; + float: left; + background: #e6e6e6; + .rangeslider__fill, .rangeslider__handle { + position: absolute; + } + &, .rangeslider__fill { + display: block; + box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3); + border-radius: 10px; + } + .rangeslider__handle { + background: #fff; + border: 1px solid #ccc; + cursor: pointer; + width: 30px; + height: 30px; + border-radius: 30px; + top: 50%; + transform: translate3d(-50%,-50%,0); + &:active { + @include themify($themes) { + background: themed('colorAccent'); + } + box-shadow: none; + } + } } - &, .rangeslider__fill { - display: block; - box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3); - border-radius: 10px; - } - .rangeslider__handle { - background: #fff; - border: 1px solid #ccc; - cursor: pointer; - width: 30px; - height: 30px; - border-radius: 30px; - top: 50%; - transform: translate3d(-50%,-50%,0); - &:active { - background: $color-teal; + .rangeslider-horizontal { + height: 10px; + width: 200px; + background: none; + float: left; + margin-top: 18px; + .rangeslider__fill { + height: 100%; + @include themify($themes) { + background: themed('colorAccent'); + } box-shadow: none; + left: 0; } } - } - .rangeslider-horizontal { - height: 10px; - width: 200px; - background: none; - float: left; - margin-top: 18px; - .rangeslider__fill { - height: 100%; - background: $color-teal; - box-shadow: none; - left: 0; + .Voting__adjust_weight_close { + position: static; + line-height: 2.6rem; + font-size: 2rem; + margin-left: 1rem; } } - .Voting__adjust_weight_close { - position: static; - line-height: 2.6rem; - font-size: 2rem; - margin-left: 1rem; - } -} -@media screen and (max-width: 39.9375em) { - .Voting__button-up { - .dropdown-pane { - margin-top: -36px; - position: absolute; - left: 1rem; + @media screen and (max-width: 39.9375em) { + .Voting__button-up { + .dropdown-pane { + margin-top: -36px; + position: absolute; + left: 1rem; + } } } -} -/* Medium and bigger */ -@media screen and (min-width: 39.9375em) { - .Voting__button-up { - display: inline-block; - position: relative; - .dropdown-pane { - top: -18px; - left: -28px; + /* Medium and bigger */ + @media screen and (min-width: 39.9375em) { + .Voting__button-up { + display: inline-block; + position: relative; + .dropdown-pane { + top: -18px; + left: -28px; + } } } -} -/* Pulse for upvote action */ -@-webkit-keyframes pulse { - 0% { - -webkit-box-shadow: 0 0 0 0 rgba(6, 214, 169, 2); - } - 70% { - -webkit-box-shadow: 0 0 0 10px rgba(6, 214, 169, 1); - } - 100% { - -webkit-box-shadow: 0 0 0 0 rgba(6, 214, 169, 1); - } -} -@keyframes pulse { - 0% { - -moz-box-shadow: 0 0 0 0 rgba(6, 214, 169, 2); - box-shadow: 0 0 0 0 rgba(6, 214, 169, 2); - } - 70% { - -moz-box-shadow: 0 0 0 10px rgba(6, 214, 169, 0); - box-shadow: 0 0 0 10px rgba(6, 214, 169, 0); - } - 100% { - -moz-box-shadow: 0 0 0 0 rgba(6, 214, 169, 0); - box-shadow: 0 0 0 0 rgba(6, 214, 169, 0); + // Beat for upvote action + @keyframes beat { + 0%, 50%, 100% { transform: scale(1, 1); } + 30%, 80% { transform: scale(1.16, 1.19); } } -} -.weight-container { - min-width: 300px; - width: 90%; - margin: 0 auto; - padding-bottom: 2px; -} + /* Pulse for upvote action */ + @-webkit-keyframes pulse { + 0% { + -webkit-box-shadow: 0 0 0 0 rgba(6, 214, 169, 1); + } + 70% { + -webkit-box-shadow: 0 0 0 10px rgba(6, 214, 169, 1); + } + 100% { + -webkit-box-shadow: 0 0 0 0 rgba(6, 214, 169, 1); + } + } + @keyframes pulse { + 0% { + -moz-box-shadow: 0 0 0 0 rgba(6, 214, 169, 2); + box-shadow: 0 0 0 0 rgba(6, 214, 169, 2); + } + 70% { + -moz-box-shadow: 0 0 0 10px rgba(6, 214, 169, 0); + box-shadow: 0 0 0 10px rgba(6, 214, 169, 0); + } + 100% { + -moz-box-shadow: 0 0 0 0 rgba(6, 214, 169, 0); + box-shadow: 0 0 0 0 rgba(6, 214, 169, 0); + } + } + + .weight-container { + min-width: 300px; + width: 90%; + margin: 0 auto; + padding-bottom: 2px; + } diff --git a/src/app/components/elements/WelcomePanel.jsx b/src/app/components/elements/WelcomePanel.jsx index 2ed812e5c..3972a1282 100644 --- a/src/app/components/elements/WelcomePanel.jsx +++ b/src/app/components/elements/WelcomePanel.jsx @@ -2,7 +2,7 @@ import React from 'react'; import CloseButton from 'app/components/elements/CloseButton'; import { Link } from 'react-router'; import tt from 'counterpart'; -import { SIGNUP_URL } from 'shared/constants'; +import { HIVE_SIGNUP_URL, SIGNUP_URL } from 'shared/constants'; export default class WelcomePanel extends React.Component { constructor(props) { @@ -12,7 +12,7 @@ export default class WelcomePanel extends React.Component { render() { const signup = ( - + {tt('navigation.sign_up')} ); @@ -27,28 +27,113 @@ export default class WelcomePanel extends React.Component {
    -
    +
    +
    + +
    -

    - Communities Without Borders +

    + A world of free speech and ownership {/*tt('navigation.intro_tagline')*/}

    - { - 'A social network owned and operated by its users, ' - } - {'powered by '} - Steem. - {/*tt('navigation.intro_paragraph')*/} + Welcome to Build-it, a springboard for DIY and + craft making lovers. Post, learn, earn.

    -
    +
    {signup} {learn}
    +
    +{/* Begin Mailchimp Signup Form */} + {/*
    */} +
    +
    +
    +

    +
    Don't Miss Out!, Subscribe to get diy updates, tips and many more!
    +
    +
    + + + +
    +
    +
    +
    +
    {" "} + {/* real people should not fill this in and expect good things - do not remove this or risk form bot signups*/} + {/*
    */} + +
    +
    + +

    + + + +

    +
    +
    +
    +
    + +
    + {/*End mc_embed_signup*/} +
    diff --git a/src/app/components/elements/YoutubePreview.jsx b/src/app/components/elements/YoutubePreview.jsx index c71f914e2..6fe1b84a2 100644 --- a/src/app/components/elements/YoutubePreview.jsx +++ b/src/app/components/elements/YoutubePreview.jsx @@ -2,12 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; -import { APP_URL } from 'app/client_config'; const { string, number } = PropTypes; /** Lots of iframes in a post can be very slow. This component only inserts the iframe when it is actually needed. */ -export default class YoutubePreview extends React.Component { +class YoutubePreview extends React.Component { static propTypes = { youTubeId: string.isRequired, width: number, @@ -20,7 +19,6 @@ export default class YoutubePreview extends React.Component { width: 640, height: 360, startTime: 0, - dataParams: `enablejsapi=0&rel=0&origin=${APP_URL}`, }; constructor() { @@ -81,3 +79,14 @@ export default class YoutubePreview extends React.Component { ); } } + +import { connect } from 'react-redux'; + +export default connect((state, ownProps) => { + const appUrl = state.app.getIn(['hostConfig', 'APP_URL']); + const dataParams = `enablejsapi=0&rel=0&origin=${appUrl}`; + return { + dataParams, + ...ownProps, + }; +})(YoutubePreview); diff --git a/src/app/components/modules/ExplorePost.jsx b/src/app/components/modules/ExplorePost.jsx index 793b549ac..6e0ef4a49 100644 --- a/src/app/components/modules/ExplorePost.jsx +++ b/src/app/components/modules/ExplorePost.jsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { APP_URL } from 'app/client_config'; import { serverApiRecordEvent } from 'app/utils/ServerApiClient'; import Icon from 'app/components/elements/Icon'; import CopyToClipboard from 'react-copy-to-clipboard'; @@ -38,8 +37,9 @@ class ExplorePost extends Component { render() { const link = this.props.permlink; const title = this.props.title; - const appLink = APP_URL + link; - const md = `[${title}](${APP_URL}${link})`; + const appUrl = this.props.appUrl; + const appLink = appUrl + link; + const md = `[${title}](${appUrl}${link})`; let text = this.state.copied == true ? tt('explorepost_jsx.copied') @@ -89,4 +89,10 @@ class ExplorePost extends Component { } } -export default connect()(ExplorePost); +export default connect((state, ownProps) => { + const appUrl = state.app.getIn(['hostConfig', 'APP_URL']); + return { + appUrl, + ...ownProps, + }; +})(ExplorePost); diff --git a/src/app/components/modules/Header/index.jsx b/src/app/components/modules/Header/index.jsx index 0851826c7..43dba05cf 100644 --- a/src/app/components/modules/Header/index.jsx +++ b/src/app/components/modules/Header/index.jsx @@ -1,3 +1,4 @@ +import { Map } from 'immutable'; import React from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router'; @@ -6,7 +7,6 @@ import { parseJsonTags } from 'app/utils/StateFunctions'; import Headroom from 'react-headroom'; import resolveRoute from 'app/ResolveRoute'; import tt from 'counterpart'; -import { APP_NAME } from 'app/client_config'; import ElasticSearchInput from 'app/components/elements/ElasticSearchInput'; import IconButton from 'app/components/elements/IconButton'; import DropdownMenu from 'app/components/elements/DropdownMenu'; @@ -15,21 +15,21 @@ import * as appActions from 'app/redux/AppReducer'; import { startPolling } from 'app/redux/PollingSaga'; import { actions as fetchDataSagaActions } from 'app/redux/FetchDataSaga'; import Userpic from 'app/components/elements/Userpic'; -import { SIGNUP_URL } from 'shared/constants'; +import { HIVE_SIGNUP_URL, SIGNUP_URL } from 'shared/constants'; import AppLogo from 'app/components/elements/AppLogo'; -import { APP_ICON } from 'app/client_config'; import normalizeProfile from 'app/utils/NormalizeProfile'; import Announcement from 'app/components/elements/Announcement'; import GptAd from 'app/components/elements/GptAd'; import ReviveAd from 'app/components/elements/ReviveAd'; import SortOrder from 'app/components/elements/SortOrder'; -import { Map } from 'immutable'; import ReactMutationObserver from '../../utils/ReactMutationObserver'; +import DarkModeBtn from '../../DarkMode/DarkModeBtn'; class Header extends React.Component { static propTypes = { current_account_name: PropTypes.string, pathname: PropTypes.string, + appName: PropTypes.string, getUnreadAccountNotifications: PropTypes.func, startNotificationsPolling: PropTypes.func, loggedIn: PropTypes.bool, @@ -135,10 +135,13 @@ class Header extends React.Component { nightmodeEnabled, showSidePanel, navigate, + appName, + preferHive, display_name, content, unreadNotificationCount, notificationActionPending, + toggleBody, } = this.props; const { showAd, showReviveAd, showAnnouncement } = this.state; @@ -238,7 +241,7 @@ class Header extends React.Component { process.env.BROWSER && (route.page !== 'Post' && route.page !== 'PostNoCategory') ) - document.title = page_title + ' — ' + APP_NAME; + document.title = page_title + ' — ' + appName; const _feed = username && `/@${username}/feed`; const logo_link = _feed && pathname != _feed ? _feed : '/'; @@ -357,6 +360,12 @@ class Header extends React.Component { />
    + {/*CUSTOM SEARCH*/} + + + + + {/*NOT LOGGED IN SIGN IN AND SIGN UP LINKS*/} {!loggedIn && ( @@ -369,7 +378,11 @@ class Header extends React.Component { {tt('g.sign_up')} @@ -378,6 +391,8 @@ class Header extends React.Component { {/*SUBMIT STORY*/} {submit_story} + {/*DARKMODE TOGGLE*/} + {/*USER AVATAR */} {loggedIn && (
  • - +
  • @@ -418,6 +436,8 @@ const mapStateToProps = (state, ownProps) => { return { username: null, loggedIn: false, + appName: state.app.getIn(['hostConfig', 'APP_NAME']), + preferHive: state.app.getIn(['hostConfig', 'PREFER_HIVE'], true), community: state.global.get('community', Map({})), }; } @@ -474,6 +494,8 @@ const mapStateToProps = (state, ownProps) => { announcement, showAnnouncement: state.user.get('showAnnouncement'), gptEnabled, + appName: state.app.getIn(['hostConfig', 'APP_NAME']), + preferHive: state.app.getIn(['hostConfig', 'PREFER_HIVE'], true), content, unreadNotificationCount, notificationActionPending: state.global.getIn([ diff --git a/src/app/components/modules/Header/styles.scss b/src/app/components/modules/Header/styles.scss index acbec17dc..49d7bdad4 100644 --- a/src/app/components/modules/Header/styles.scss +++ b/src/app/components/modules/Header/styles.scss @@ -1,199 +1,190 @@ + .Header { - backface-visibility: hidden; - top: 0; - left: 0; - width: 100%; - z-index: 100; - box-shadow: 0 2px 4px 0 rgba(0,0,0,0.05); - @include themify($themes) { - background-color: themed('navBackgroundColor'); - border-bottom: themed('border'); + backface-visibility: hidden; + top: 0; + left: 0; + width: 100%; + z-index: 100; + box-shadow: 0 2px 4px 0 rgba(0,0,0,0.05); + @include themify($themes) { + background-color: themed('navBackgroundColor'); + border-bottom: themed('border'); + } } -} -.Header__nav { - display: flex; - align-items: center; - height: 4rem; - max-width: none; -} + .Header__nav { + display: flex; + align-items: center; + height: 4rem; + max-width: none; + } -.ConnectionError { - margin-right: 4rem; - color: #ec5840; -} + .ConnectionError { + margin-right: 4rem; + color: #ec5840; + } -.Header__logotype { - transition: 0.2s all ease-in-out; - height: 37px; - display: flex; - align-items: baseline; - .icon-svg { - @include themify($themes) { - fill: themed('colorAccent'); + .Header__logotype { + transition: 0.2s all ease-in-out; + height: 37px; + display: flex; + align-items: baseline; + .icon-svg { + @include themify($themes) { + fill: themed('colorAccent'); + } + } + + &-beta{ + position: absolute; + top: 38px; + left: 136px; } } - &-beta{ - position: absolute; - top: 38px; - left: 136px; + .Header__sort { + display: flex; + justify-content: center; } -} -.Header__sort { - display: flex; - justify-content: center; -} -.Header__search { - @include MQ(L) { - display: none; - } - &--desktop { - display: none; - @include MQ(L) { - display: block; - } - } -} -.Header__buttons { - display: flex; - align-items: center; - justify-content: flex-end; - height: 4rem; - > .Header__user-signup { - > a { - padding-right: 0.25rem; - font-size: 1.125rem; - font-family: $font-primary; - &.Header__signup-link { - @extend .e-btn; - padding: 0.6rem; - text-transform: none; - margin: 0 0.75rem 0 .5rem; - white-space: nowrap; - } - &.Header__login-link { - @extend .link; - @extend .link--primary; - } - } - } - > a { - padding-left: 0.25rem; - padding-right: 0.25rem; - @include MQ(S) { - padding-left: 0.5rem; - padding-right: 0.5rem; - } - } - div.LoadingIndicator { - padding-right: 0.75rem; - } -} + .Header__buttons { + display: flex; + align-items: center; + justify-content: flex-end; + height: 4rem; + > .Header__user-signup { + > a { + padding-right: 0.25rem; + font-size: 1.125rem; + font-family: $font-primary; + &.Header__signup-link { + @extend .e-btn; + padding: 0.6rem; + text-transform: none; + margin: 0 0.75rem 0 .5rem; + white-space: nowrap; + } + &.Header__login-link { + @extend .link; + @extend .link--primary; + } + } + } + > a { + padding-left: 0.25rem; + padding-right: 0.25rem; + @include MQ(S) { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + } + div.LoadingIndicator { + padding-right: 0.75rem; + } + } -.Header__userpic { - display: block; - width: 36px; - height: 36px; - .Userpic { - width: 36px !important; - height: 36px !important; - @include MQ(M) { - width: 40px!important; - height: 40px !important; - position: relative; - top: -2px; + .Header__userpic { + display: block; + width: 36px; + height: 36px; + .Userpic { + width: 36px !important; + height: 36px !important; + @include MQ(M) { + width: 40px!important; + height: 40px !important; + position: relative; + top: -2px; + } } } -} -.Header__notification { - position: absolute; - width: 20px; - height: 20px; - top: -10px; - right: -10px; - border-radius: 50%; - background: red; - line-height: 20px; - font-size: 11px; - text-align: center; - > span { - color: white; - } - &--loading { - background: transparent; - } -} + .Header__notification { + position: absolute; + width: 20px; + height: 20px; + top: -10px; + right: -10px; + border-radius: 50%; + background: red; + line-height: 20px; + font-size: 11px; + text-align: center; + > span { + color: white; + } + &--loading { + background: transparent; + } + } -span.Header__hamburger.toggle-menu { - width: 1rem; - height: 1rem; - @include hamburger(); - // This margin is to prevent user avatar overlapping the hamburger in the header. - margin-left: 0.25rem; - @include MQ(S) { - margin-left: 0.5rem; - } - @include MQ(M) { - margin-left: 0.75rem; - } - cursor: pointer; - &::after { - transition: 0.2s all ease-in-out; - @include themify($themes) { - background: themed('textColorPrimary'); - box-shadow: 0 7px 0 themed('textColorPrimary'), 0 14px 0 themed('textColorPrimary'); + span.Header__hamburger.toggle-menu { + width: 1rem; + height: 1rem; + @include hamburger(); + // This margin is to prevent user avatar overlapping the hamburger in the header. + margin-left: 0.25rem; + @include MQ(S) { + margin-left: 0.5rem; } - } - &:hover { + @include MQ(M) { + margin-left: 0.75rem; + } + cursor: pointer; &::after { + transition: 0.2s all ease-in-out; @include themify($themes) { - background: themed('textColorAccent'); - box-shadow: 0 7px 0 themed('textColorAccent'), 0 14px 0 themed('textColorAccent'); + background: themed('textColorPrimary'); + box-shadow: 0 7px 0 themed('textColorPrimary'), 0 14px 0 themed('textColorPrimary'); + } + } + &:hover { + &::after { + @include themify($themes) { + background: themed('textColorAccent'); + box-shadow: 0 7px 0 themed('textColorAccent'), 0 14px 0 themed('textColorAccent'); + } } } - } -} - -.annoucement-banner { - text-align: left; - position: relative; - background: #171FC9; //Notice Blue - // background: #fff3cd; - color: #fff; - // color: #856404; - @include themify($themes) { - border-bottom: themed('borderLight'); - } - @include MQ(M) { - text-align: center; } - .close-button { - position: absolute; - top: 2px; - right: 0px; - transform: scale(0.85); + + .annoucement-banner { + text-align: left; + position: relative; + background: #171FC9; //Notice Blue + // background: #fff3cd; color: #fff; - &:hover, &:focus { - color: #ccc; + // color: #856404; + @include themify($themes) { + border-bottom: themed('borderLight'); + } + @include MQ(M) { + text-align: center; + } + .close-button { + position: absolute; + top: 2px; + right: 0px; + transform: scale(0.85); + color: #fff; + &:hover, &:focus { + color: #ccc; + } } } -} -.announcement-banner__text { - margin: 0; - padding: 10px 44px 10px 10px; - font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.3; - } + .announcement-banner__text { + margin: 0; + padding: 10px 44px 10px 10px; + font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.3; + } -.announcement-banner__link { - color: #fafafa !important; - // color: #856404 !important;; - text-decoration: underline; -} + .announcement-banner__link { + color: #fafafa !important; + // color: #856404 !important;; + text-decoration: underline; + } diff --git a/src/app/components/modules/LoginForm.jsx b/src/app/components/modules/LoginForm.jsx index 5933fe46c..9e76ae8d1 100644 --- a/src/app/components/modules/LoginForm.jsx +++ b/src/app/components/modules/LoginForm.jsx @@ -12,9 +12,8 @@ import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; import reactForm from 'app/utils/ReactForm'; import { serverApiRecordEvent } from 'app/utils/ServerApiClient'; import tt from 'counterpart'; -import { APP_URL, DISABLE_HIVE } from 'app/client_config'; import { PrivateKey, PublicKey } from '@hiveio/hive-js/lib/auth/ecc'; -import { SIGNUP_URL } from 'shared/constants'; +import { HIVE_SIGNUP_URL, SIGNUP_URL } from 'shared/constants'; import PdfDownload from 'app/components/elements/PdfDownload'; import { hiveSignerClient } from 'app/utils/HiveSigner'; import { getQueryStringParams } from 'app/utils/Links'; @@ -108,8 +107,9 @@ class LoginForm extends Component { SignUp() { const onType = document.getElementsByClassName('OpAction')[0] .textContent; - serverApiRecordEvent('FreeMoneySignUp', onType); - window.location.href = SIGNUP_URL; + window.location.href = this.props.preferHive + ? HIVE_SIGNUP_URL + : SIGNUP_URL; } useKeychainToggle = () => { @@ -165,6 +165,7 @@ class LoginForm extends Component { }; render() { + const appUrl = this.props.appUrl; if (!process.env.BROWSER) { return (
    @@ -194,7 +195,7 @@ class LoginForm extends Component { {' '} {tt( 'loginform_jsx.are_well_tested_and_known_to_work_with', - { APP_URL } + { appUrl } )}

    @@ -226,6 +227,7 @@ class LoginForm extends Component { hideWarning, afterLoginRedirectToWelcome, msg, + disableHive, } = this.props; const { username, password, useKeychain, saveLogin } = this.state; const { valid, handleSubmit } = this.state.login; @@ -510,7 +512,7 @@ class LoginForm extends Component { ); - const moreLoginMethods = DISABLE_HIVE ? null : ( + const moreLoginMethods = disableHive ? null : (
    { const walletUrl = state.app.get('walletUrl'); + const appUrl = state.app.getIn(['hostConfig', 'APP_URL']); + const preferHive = state.app.getIn(['hostConfig', 'PREFER_HIVE'], true); + const disableHive = state.app.getIn(['hostConfig', 'DISABLE_HIVE']); const showLoginWarning = state.user.get('show_login_warning'); const loginError = state.user.get('login_error'); const currentUser = state.user.get('current'); @@ -620,6 +625,9 @@ export default connect( initialValues, initialUsername, msg, + appUrl, + preferHive, + disableHive, offchain_user: state.offchain.get('user'), }; }, diff --git a/src/app/components/modules/LoginForm.scss b/src/app/components/modules/LoginForm.scss index 49474ff09..ad38dd6d7 100644 --- a/src/app/components/modules/LoginForm.scss +++ b/src/app/components/modules/LoginForm.scss @@ -35,8 +35,10 @@ @include font-size(16px); &:hover { - border: 1px solid $color-teal-dark; - color: $color-teal-dark; + @include themify($themes) { + border: 1px solid themed('colorAccent'); + color: themed('colorAccent'); + } box-shadow: 0 2px 4px 0 rgba(0,0,0,0.05); } } diff --git a/src/app/components/modules/PostAdvancedSettings.jsx b/src/app/components/modules/PostAdvancedSettings.jsx index 4e291d97c..6fe87a72b 100644 --- a/src/app/components/modules/PostAdvancedSettings.jsx +++ b/src/app/components/modules/PostAdvancedSettings.jsx @@ -29,7 +29,7 @@ class PostAdvancedSettings extends Component { } initForm(props) { - const { fields } = props; + const { fields, defaultBeneficiaryPercent } = props; reactForm({ fields, instance: this, @@ -40,7 +40,8 @@ class PostAdvancedSettings extends Component { beneficiaries: validateBeneficiaries( props.username, values.beneficiaries, - false + false, + defaultBeneficiaryPercent ), }; }, @@ -394,6 +395,10 @@ export default connect( 'beneficiaries', ]); beneficiaries = beneficiaries ? beneficiaries.toJS() : []; + const defaultBeneficiaryPercent = state.app.getIn( + ['hostConfig', 'SCOT_DEFAULT_BENEFICIARY_PERCENT'], + 0 + ); return { ...ownProps, fields: ['beneficiaries'], @@ -402,6 +407,7 @@ export default connect( initialMaxAcceptedPayout, username, initialValues: { beneficiaries }, + defaultBeneficiaryPercent, }; }, diff --git a/src/app/components/modules/Powerdown.jsx b/src/app/components/modules/Powerdown.jsx index 0dbfa6b3f..fc76216d4 100644 --- a/src/app/components/modules/Powerdown.jsx +++ b/src/app/components/modules/Powerdown.jsx @@ -7,11 +7,6 @@ import * as globalActions from 'app/redux/GlobalReducer'; import { actions as userProfileActions } from 'app/redux/UserProfilesSaga'; import * as transactionActions from 'app/redux/TransactionReducer'; import * as userActions from 'app/redux/UserReducer'; -import { - LIQUID_TOKEN_UPPERCASE, - VESTING_TOKEN, - HIVE_ENGINE, -} from 'app/client_config'; import { numberWithCommas } from 'app/utils/StateFunctions'; class Powerdown extends React.Component { @@ -34,6 +29,7 @@ class Powerdown extends React.Component { delegatedStake, lockedStake, scotPrecision, + scotTokenSymbol, useHive, } = this.props; const sliderChange = value => { @@ -67,6 +63,7 @@ class Powerdown extends React.Component { } const unstakeAmount = String(withdraw.toFixed(scotPrecision)); this.props.withdrawVesting({ + scotTokenSymbol, account, unstakeAmount, errorCallback, @@ -85,7 +82,7 @@ class Powerdown extends React.Component {
  • {tt('powerdown_jsx.delegating', { AMOUNT, - LIQUID_TICKER: LIQUID_TOKEN_UPPERCASE, + LIQUID_TICKER: scotTokenSymbol, })}
  • ); @@ -135,7 +132,7 @@ class Powerdown extends React.Component { onChange={inputChange} autoCorrect={false} /> - {LIQUID_TOKEN_UPPERCASE} + {scotTokenSymbol}

      {notes}
    @@ -126,9 +121,7 @@ class PromotePost extends Component {
    {tt('g.balance', { - balanceValue: `${balance} ${ - LIQUID_TOKEN_UPPERCASE - }`, + balanceValue: `${balance} ${scotTokenSymbol}`, })}

    @@ -159,13 +152,14 @@ class PromotePost extends Component { } } -// const AssetBalance = ({onClick, balanceValue}) => -//
    Balance: {balanceValue} - export default connect( (state, ownProps) => { const currentUser = state.user.getIn(['current']); - const hiveEngine = HIVE_ENGINE; + const scotTokenSymbol = state.app.getIn([ + 'hostConfig', + 'LIQUID_TOKEN_UPPERCASE', + ]); + const hiveEngine = state.app.getIn(['hostConfig', 'HIVE_ENGINE']); const promotedPostAccount = state.app.getIn( ['scotConfig', 'config', 'promoted_post_account'], 'null' @@ -173,6 +167,7 @@ export default connect( return { ...ownProps, currentUser, + scotTokenSymbol, promotedPostAccount, hiveEngine, }; @@ -181,8 +176,8 @@ export default connect( // mapDispatchToProps dispatch => ({ dispatchSubmit: ({ + scotTokenSymbol, amount, - asset, author, permlink, hive, @@ -204,7 +199,7 @@ export default connect( contractName: 'tokens', contractAction: 'transfer', contractPayload: { - symbol: LIQUID_TOKEN_UPPERCASE, + symbol: scotTokenSymbol, to: promotedPostAccount, quantity: amount, memo: `${hive && !hiveEngine ? 'h' : ''}@${author}/${ diff --git a/src/app/components/modules/Settings.jsx b/src/app/components/modules/Settings.jsx index 2f275c70d..e517d2ecd 100644 --- a/src/app/components/modules/Settings.jsx +++ b/src/app/components/modules/Settings.jsx @@ -766,6 +766,9 @@ class Settings extends React.Component { + @@ -1048,7 +1051,7 @@ export default connect( const metaData = read_profile_v2(account); const profile = metaData && metaData.profile ? metaData.profile : {}; const user_preferences = state.app.get('user_preferences').toJS(); - const useHive = PREFER_HIVE; + const useHive = state.app.getIn(['hostConfig', 'PREFER_HIVE']); return { account, diff --git a/src/app/components/modules/SidePanel/index.jsx b/src/app/components/modules/SidePanel/index.jsx index 13ef0a62f..f2308b09f 100644 --- a/src/app/components/modules/SidePanel/index.jsx +++ b/src/app/components/modules/SidePanel/index.jsx @@ -2,12 +2,11 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import tt from 'counterpart'; -import { HIVE_ENGINE, LIQUID_TOKEN_UPPERCASE } from 'app/client_config'; import * as appActions from 'app/redux/AppReducer'; import CloseButton from 'app/components/elements/CloseButton'; import Icon from 'app/components/elements/Icon'; import { Link } from 'react-router'; -import { SIGNUP_URL } from 'shared/constants.js'; +import { HIVE_SIGNUP_URL, SIGNUP_URL } from 'shared/constants'; const SidePanel = ({ alignment, @@ -60,15 +59,33 @@ const SidePanel = ({ internal: [ { value: 'engine', - label: useHive ? 'Tribaldex' : 'Steem Engine', - link: useHive ? `https://tribaldex.com/trade/${scotTokenSymbol}` : `https://steem-engine.net/?p=market&t=${scotTokenSymbol}`, + label: 'Leo Dex', + link: 'https://leodex.io/market/BUIDL', + }, + { + value: 'engine', + label: 'Hive Engine', + link: 'https://hive-engine.com/trade/BUIDL', + }, + { + value: 'engine', + label: 'Tribal Dex', + link: 'https://tribaldex.com/trade/BUIDL', + }, + { + value: 'engine', + label: 'FAQ', + link: '/faq.html', }, ], - external: [ { - label: tt('navigation.chat'), - link: 'https://openhive.chat/home', + label: 'LitePaper', + link: 'https://eu.docworkspace.com/d/sIHWUtOWLAYXB1pIG', + }, + { + label: 'FAQ', + link: '/faq.html', }, ], @@ -76,12 +93,9 @@ const SidePanel = ({ legal: [ { - label: tt('navigation.privacy_policy'), - link: '/privacy.html', - }, - { - label: tt('navigation.terms_of_service'), - link: '/tos.html', + value: 'engine', + label: 'NFTs', + link: 'https://nftshowroom.com/build-it/gallery', }, ], @@ -92,7 +106,7 @@ const SidePanel = ({ }, { label: tt('g.sign_up'), - link: SIGNUP_URL, + link: useHive ? HIVE_SIGNUP_URL : SIGNUP_URL, }, { value: 'post', @@ -100,6 +114,14 @@ const SidePanel = ({ link: '/submit.html', }, ], + extras_WEED: [ + { + value: 'whitepaper', + label: 'White Paper', + internal: true, + link: '/@coffeebuds/weedcash-network-white-paper', + }, + ], }; return ( @@ -109,13 +131,52 @@ const SidePanel = ({
      {sidePanelLinks.extras.map(makeLink)}
    + + {sidePanelLinks['extras_' + scotTokenSymbol] && ( +
      + {sidePanelLinks['extras_' + scotTokenSymbol].map( + makeLink + )} +
    + )} + + {sidePanelLinks['organizational_' + scotTokenSymbol] && ( +
      +
    • + Community +
    • + {sidePanelLinks[ + 'organizational_' + scotTokenSymbol + ].map(makeLink)} +
    + )} + +
      +
    • + Buy {'$buidl'} +
    • + {(sidePanelLinks['internal_' + scotTokenSymbol] + ? sidePanelLinks['internal_' + scotTokenSymbol] + : sidePanelLinks['internal'] + ).map(makeLink)} +
    +
      +
    • + Resources +
    • + {(sidePanelLinks['external_' + scotTokenSymbol] + ? sidePanelLinks['external_' + scotTokenSymbol] + : sidePanelLinks['external'] + ).map(makeLink)} +
      -
    • - - Trade {LIQUID_TOKEN_UPPERCASE} - -
    • - {sidePanelLinks['internal'].map(makeLink)} +
    • + Buy Arts +
    • + {(sidePanelLinks['legal_' + scotTokenSymbol] + ? sidePanelLinks['legal_' + scotTokenSymbol] + : sidePanelLinks['legal'] + ).map(makeLink)}
    @@ -127,6 +188,7 @@ SidePanel.propTypes = { visible: PropTypes.bool.isRequired, hideSidePanel: PropTypes.func.isRequired, username: PropTypes.string, + scotTokenSymbol: PropTypes.string, toggleNightmode: PropTypes.func.isRequired, }; @@ -137,8 +199,11 @@ SidePanel.defaultProps = { export default connect( (state, ownProps) => { const walletUrl = state.app.get('walletUrl'); - const scotTokenSymbol = LIQUID_TOKEN_UPPERCASE; - const useHive = HIVE_ENGINE; + const scotTokenSymbol = state.app.getIn([ + 'hostConfig', + 'LIQUID_TOKEN_UPPERCASE', + ]); + const useHive = state.app.getIn(['hostConfig', 'HIVE_ENGINE'], true); return { walletUrl, scotTokenSymbol, diff --git a/src/app/components/modules/SidePanel/styles.scss b/src/app/components/modules/SidePanel/styles.scss index 13da1d744..bd675c47e 100644 --- a/src/app/components/modules/SidePanel/styles.scss +++ b/src/app/components/modules/SidePanel/styles.scss @@ -28,10 +28,14 @@ $menu-width: 250px; } > a:hover, &:focus { background-color: $color-blue-black; - border-bottom: 1px solid $color-teal; + @include themify($themes) { + border-bottom: 1px solid themed('colorAccent'); + } path { - fill: $color-teal; + @include themify($themes) { + fill: themed('colorAccent'); + } } } path { diff --git a/src/app/components/modules/Transfer.jsx b/src/app/components/modules/Transfer.jsx index 4bcee8a24..400690573 100644 --- a/src/app/components/modules/Transfer.jsx +++ b/src/app/components/modules/Transfer.jsx @@ -17,13 +17,6 @@ import { validate_memo_field, } from 'app/utils/ChainValidation'; import { countDecimals } from 'app/utils/ParsersAndFormatters'; -import { - APP_NAME, - LIQUID_TOKEN, - LIQUID_TOKEN_UPPERCASE, - VESTING_TOKEN, - HIVE_ENGINE, -} from 'app/client_config'; /** Warning .. This is used for Power UP too. */ @@ -34,6 +27,7 @@ class TransferForm extends Component { toVesting: PropTypes.bool.isRequired, currentAccount: PropTypes.object.isRequired, following: PropTypes.object.isRequired, + hostConfig: PropTypes.object, }; constructor(props) { @@ -187,13 +181,13 @@ class TransferForm extends Component { balanceValue() { const { transferType } = this.props.initialValues; - const { currentAccount, tokenBalances } = this.props; + const { currentAccount, tokenBalances, hostConfig } = this.props; const { asset } = this.state; const tokenBalance = tokenBalances.find( - ({ symbol }) => symbol === LIQUID_TOKEN_UPPERCASE + ({ symbol }) => symbol === hostConfig['LIQUID_TOKEN_UPPERCASE'] ); - return !asset || asset.value === LIQUID_TOKEN_UPPERCASE - ? `${tokenBalance.balance} ${LIQUID_TOKEN_UPPERCASE}` + return !asset || asset.value === hostConfig['LIQUID_TOKEN_UPPERCASE'] + ? `${tokenBalance.balance} ${hostConfig['LIQUID_TOKEN_UPPERCASE']}` : null; } @@ -211,7 +205,21 @@ class TransferForm extends Component { }; render() { - const useHive = HIVE_ENGINE; + const { + currentUser, + currentAccount, + tokenBalances, + hostConfig, + toVesting, + transferToSelf, + dispatchSubmit, + } = this.props; + + const APP_NAME = hostConfig['APP_NAME']; + const LIQUID_TOKEN = hostConfig['LIQUID_TOKEN']; + const LIQUID_TOKEN_UPPERCASE = hostConfig['LIQUID_TOKEN_UPPERCASE']; + const VESTING_TOKEN = hostConfig['VESTING_TOKEN']; + const useHive = hostConfig['HIVE_ENGINE']; const transferTips = { 'Transfer to Account': tt( 'transfer_jsx.move_funds_to_another_account', @@ -224,14 +232,7 @@ class TransferForm extends Component { ); const { to, amount, asset, memo } = this.state; const { loading, trxError, advanced } = this.state; - const { - currentUser, - currentAccount, - tokenBalances, - toVesting, - transferToSelf, - dispatchSubmit, - } = this.props; + const { transferType } = this.props.initialValues; const { submitting, valid, handleSubmit } = this.state.transfer; // const isMemoPrivate = memo && /^#/.test(memo.value); -- private memos are not supported yet @@ -247,6 +248,7 @@ class TransferForm extends Component { currentUser, toVesting, transferType, + symbol: LIQUID_TOKEN_UPPERCASE, useHive, }); })} @@ -540,8 +542,9 @@ import { connect } from 'react-redux'; export default connect( // mapStateToProps (state, ownProps) => { + const hostConfig = state.app.get('hostConfig', Map()).toJS(); const initialValues = state.user.get('transfer_defaults', Map()).toJS(); - const toVesting = initialValues.asset === VESTING_TOKEN; + const toVesting = initialValues.asset === hostConfig['VESTING_TOKEN']; const currentUser = state.user.getIn(['current']); const currentAccount = state.userProfiles.getIn([ 'profiles', @@ -583,6 +586,7 @@ export default connect( 'blog_result', ]), initialValues, + hostConfig, }; }, @@ -595,6 +599,7 @@ export default connect( memo, transferType, toVesting, + symbol, currentUser, errorCallback, useHive, @@ -619,7 +624,7 @@ export default connect( contractName: 'tokens', contractAction: 'stake', contractPayload: { - symbol: LIQUID_TOKEN_UPPERCASE, + symbol, to, quantity: amount, }, @@ -628,7 +633,7 @@ export default connect( contractName: 'tokens', contractAction: 'transfer', contractPayload: { - symbol: LIQUID_TOKEN_UPPERCASE, + symbol, to, quantity: amount, memo: memo ? memo : '', diff --git a/src/app/components/modules/UserWallet.jsx b/src/app/components/modules/UserWallet.jsx index 8e0a85c6d..2dbff6753 100644 --- a/src/app/components/modules/UserWallet.jsx +++ b/src/app/components/modules/UserWallet.jsx @@ -1,3 +1,4 @@ +import { Map } from 'immutable'; /* eslint react/prop-types: 0 */ import React from 'react'; import { connect } from 'react-redux'; @@ -17,13 +18,6 @@ import { import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; import Tooltip from 'app/components/elements/Tooltip'; import { FormattedHTMLMessage } from 'app/Translator'; -import { - LIQUID_TOKEN, - LIQUID_TOKEN_UPPERCASE, - VESTING_TOKEN, - USE_HIVE, - HIVE_ENGINE, -} from 'app/client_config'; import * as transactionActions from 'app/redux/TransactionReducer'; import * as globalActions from 'app/redux/GlobalReducer'; import * as appActions from 'app/redux/AppReducer'; @@ -32,12 +26,14 @@ import DropdownMenu from 'app/components/elements/DropdownMenu'; import Icon from 'app/components/elements/Icon'; import classNames from 'classnames'; import FormattedAssetTokens from 'app/components/elements/FormattedAssetTokens'; +import axios from 'axios'; class UserWallet extends React.Component { constructor() { super(); this.state = { claimInProgress: false, + buidlprice:0, }; this.onShowDepositSteem = e => { if (e && e.preventDefault) e.preventDefault(); @@ -92,9 +88,9 @@ class UserWallet extends React.Component { } handleClaimRewards = profile => { - const { claimRewards, useHive } = this.props; + const { scotTokenSymbol, claimRewards, useHive } = this.props; this.setState({ claimInProgress: true }); // disable the claim button - claimRewards(profile, useHive); + claimRewards(profile, scotTokenSymbol, useHive); }; handleClaimTokenRewards = token => { const { profile, claimTokenRewards, useHive } = this.props; @@ -119,8 +115,41 @@ class UserWallet extends React.Component { current_user, gprops, scotPrecision, + scotTokenName, + scotTokenSymbol, + scotVestingToken, useHive, } = this.props; + //axios with buidl token + axios + .request({ + method: 'POST', + url: 'https://ha.herpc.dtools.dev/contracts', + headers: { 'Content-Type': 'application/json' }, + data: { + jsonrpc: '2.0', + id: 1, + method: 'findOne', + params: { + contract: 'market', + table: 'metrics', + query: { symbol: 'BUIDL' }, + offset: 0, + limit: 1000, + }, + }, + }) + .then(response => { + let buidlToken = response.data.result.lastPrice.toLocaleString( + 'en-US' + ); + this.setState({ + buidlprice: buidlToken, + }); + }) + .catch(function(error) { + console.error(error); + }); // do not render if profile is not loaded or available if ( @@ -133,15 +162,15 @@ class UserWallet extends React.Component { ? profile.get('token_balances').toJS() : []; const tokenBalances = allTokenBalances.find( - ({ symbol }) => symbol === LIQUID_TOKEN_UPPERCASE + ({ symbol }) => symbol === scotTokenSymbol ) || { balance: '0', stake: '0', pendingUnstake: '0', - symbol: LIQUID_TOKEN_UPPERCASE, + symbol: scotTokenSymbol, }; const otherTokenBalances = allTokenBalances - .filter(({ symbol }) => symbol !== LIQUID_TOKEN_UPPERCASE) + .filter(({ symbol }) => symbol !== scotTokenSymbol) .sort((a, b) => (a.symbol > b.symbol ? 1 : -1)); const tokenUnstakes = profile.has('token_unstakes') ? profile.get('token_unstakes').toJS() @@ -230,7 +259,7 @@ class UserWallet extends React.Component { link: '#', onClick: showTransfer.bind( this, - LIQUID_TOKEN_UPPERCASE, + scotTokenSymbol, 'Transfer to Account' ), }, @@ -239,7 +268,7 @@ class UserWallet extends React.Component { link: '#', onClick: showTransfer.bind( this, - VESTING_TOKEN, + scotVestingToken, 'Transfer to Account' ), }, @@ -249,7 +278,7 @@ class UserWallet extends React.Component { value: tt('userwallet_jsx.market'), link: `https://${ useHive ? 'hive' : 'steem' - }-engine.com/?p=market&t=${LIQUID_TOKEN_UPPERCASE}`, + }-engine.com/?p=market&t=${scotTokenSymbol}`, }); } let power_menu = [ @@ -280,8 +309,7 @@ class UserWallet extends React.Component { ); const reward = tokenStatus.pending_token / Math.pow(10, scotPrecision); - const rewards_str = - reward > 0 ? `${reward} ${LIQUID_TOKEN_UPPERCASE}` : null; + const rewards_str = reward > 0 ? `${reward} ${scotTokenSymbol}` : null; let claimbox; if (current_user && rewards_str && isMyAccount) { @@ -388,7 +416,9 @@ class UserWallet extends React.Component { const power_balance_str = numberWithCommas(vesting_steem.toFixed(3)); const native_token_str = useHive ? 'Hive' : 'Steem'; const native_token_str_upper = useHive ? 'HIVE' : 'STEEM'; - + const sbs = Number(stake_balance_str.split(" ").join("")); + const EAV = sbs + parseFloat(balance_str); + const TotalEav = EAV * this.state.buidlprice; return (
    {claimbox} @@ -403,11 +433,14 @@ class UserWallet extends React.Component {
    - {LIQUID_TOKEN_UPPERCASE} + {scotTokenSymbol}
    @@ -416,18 +449,16 @@ class UserWallet extends React.Component { className="Wallet_dropdown" items={balance_menu} el="li" - selected={`${balance_str} ${ - LIQUID_TOKEN_UPPERCASE - }`} + selected={`${balance_str} ${scotTokenSymbol}`} /> ) : ( - `${balance_str} ${LIQUID_TOKEN_UPPERCASE}` + `${balance_str} ${scotTokenSymbol}` )}
    - {VESTING_TOKEN} + {scotVestingToken} ) : ( - `${stake_balance_str} ${LIQUID_TOKEN_UPPERCASE}` + `${stake_balance_str} ${scotTokenSymbol}` )} {netDelegatedStake != 0 ? (
    ({received_stake_balance_str}{' '} - {LIQUID_TOKEN_UPPERCASE}) + {scotTokenSymbol}) Pending unstake: {pending_unstake_balance_str}{' '} - {LIQUID_TOKEN_UPPERCASE}. + {scotTokenSymbol}.
    )}
    - {/* STEEM */} + {/* Savings */}
    - {native_token_str_upper} - -
    -
    - {isMyAccount ? ( - - ) : ( - `${steem_balance_str} ${native_token_str_upper}` - )} -
    -
    - {/* STEEM POWER */} -
    -
    - {native_token_str_upper} POWER - - {delegated_steem != 0 ? ( - - {tt( - 'tips_js.part_of_your_hive_power_is_currently_delegated', - { user_name: profile.get('name') } - )} - - ) : null} + SAVINGS +
    + Balances subject to 3 day withdraw waiting period. +

    HBD interest rate: 20.00% APR (as voted by the Witnesses)

    +
    - {isMyAccount ? ( - - ) : ( - `${power_balance_str} ${native_token_str_upper}` - )} - {delegated_steem != 0 ? ( -
    - - ({received_power_balance_str}{' '} - {native_token_str_upper}) - -
    - ) : null} + 0.000 {scotTokenSymbol} +

    $0.000

    - {/* Steem Dollars */} +
    + {/* Dollars Estimated Value*/}
    - {native_token_str_upper} DOLLARS + Estimated Account Value
    - {tt('userwallet_jsx.tradeable_tokens_transferred')} + The estimated value is based on an average value of Buidl in US dollars.
    - {isMyAccount ? ( - - ) : ( - sbd_balance_str - )} + ${(TotalEav.toFixed(8))}

    @@ -599,7 +556,7 @@ class UserWallet extends React.Component { })} >
    - {native_token_str} Engine Tokens + {useHive ? 'Hive' : 'Steem'} Engine Tokens
    {isMyAccount && (
    @@ -690,19 +647,31 @@ export default connect( (state, ownProps) => { const gprops = state.global.get('props'); const scotConfig = state.app.get('scotConfig'); - const useHive = HIVE_ENGINE; + const scotTokenName = state.app.getIn(['hostConfig', 'LIQUID_TOKEN']); + const scotTokenSymbol = state.app.getIn([ + 'hostConfig', + 'LIQUID_TOKEN_UPPERCASE', + ]); + const scotVestingToken = state.app.getIn([ + 'hostConfig', + 'VESTING_TOKEN', + ]); + const useHive = state.app.getIn(['hostConfig', 'HIVE_ENGINE']); return { ...ownProps, gprops: gprops ? gprops.toJS() : {}, scotPrecision: scotConfig.getIn(['info', 'precision'], 0), + scotTokenName, + scotTokenSymbol, + scotVestingToken, useHive, }; }, // mapDispatchToProps dispatch => ({ - claimRewards: (profile, useHive) => { - const username = profile.get('name'); + claimRewards: (account, scotTokenSymbol, useHive) => { + const username = account.get('name'); const successCallback = () => { dispatch( userProfileActions.fetchWalletProfile({ @@ -715,7 +684,7 @@ export default connect( id: 'scot_claim_token', required_posting_auths: [username], json: JSON.stringify({ - symbol: LIQUID_TOKEN_UPPERCASE, + symbol: scotTokenSymbol, }), }; diff --git a/src/app/components/modules/chat/ChatConversation.jsx b/src/app/components/modules/chat/ChatConversation.jsx index c650df764..d8ee0c2cc 100644 --- a/src/app/components/modules/chat/ChatConversation.jsx +++ b/src/app/components/modules/chat/ChatConversation.jsx @@ -157,7 +157,7 @@ class ChatMain extends React.PureComponent { {!chatMessages ? [] : chatMessages.toJS().map((chatMessage, index) => ( diff --git a/src/app/components/modules/chat/ChatWrapper.jsx b/src/app/components/modules/chat/ChatWrapper.jsx index a88ee990a..a8d1cab74 100644 --- a/src/app/components/modules/chat/ChatWrapper.jsx +++ b/src/app/components/modules/chat/ChatWrapper.jsx @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import { ThemeProvider, FixedWrapper, darkTheme } from '@livechat/ui-kit'; import MinimizedIcon from 'app/components/modules/chat/MinimizedIcon'; import ChatMain from 'app/components/modules/chat/ChatMain'; -import { CHAT_CONVERSATIONS } from 'app/client_config'; import * as chatActions from 'app/redux/ChatReducer'; class ChatWrapper extends React.PureComponent { @@ -42,7 +41,7 @@ class ChatWrapper extends React.PureComponent { } render() { - const { nightmodeEnabled, chatList } = this.props; + const { preferHive, defaultChatConversations, chatList, nightmodeEnabled } = this.props; const { selectedConversationId, newConversation } = this.state; const setConversation = (selectedConversationId) => this.setState({ selectedConversationId }); const setNewConversation = (newConversation) => this.setState({ newConversation }); @@ -52,7 +51,7 @@ class ChatWrapper extends React.PureComponent { height: '600px', }, }; - if (!CHAT_CONVERSATIONS || !this.props.currentUsername) { + if (!preferHive || !defaultChatConversations || !this.props.currentUsername) { return null; } const conversation = selectedConversationId ? chatList.find(c => c.get('id') === selectedConversationId) : null; @@ -74,6 +73,8 @@ class ChatWrapper extends React.PureComponent { export default connect( (state, ownProps) => { const currentUsername = state.user.getIn(['current', 'username']); + const defaultChatConversations = state.app.getIn(['hostConfig', 'CHAT_CONVERSATIONS']); + const preferHive = state.app.getIn(['hostConfig', 'PREFER_HIVE']); const initiatedChat = state.chat.get('initiateChat'); const accessToken = state.chat.getIn(['accessToken', currentUsername]); const chatList = state.chat.get('chatList'); @@ -81,7 +82,9 @@ export default connect( const loading = !accessToken || !chatList || socketState !== 'ready'; return { ...ownProps, + preferHive, currentUsername, + defaultChatConversations, nightmodeEnabled: state.app.getIn(['user_preferences', 'nightmode']), accessToken, chatList, diff --git a/src/app/components/pages/About.jsx b/src/app/components/pages/About.jsx index c4e971d65..21cb6e0d9 100644 --- a/src/app/components/pages/About.jsx +++ b/src/app/components/pages/About.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { APP_NAME, APP_URL } from 'app/client_config'; import tt from 'counterpart'; class About extends React.Component { diff --git a/src/app/components/pages/Index.jsx b/src/app/components/pages/Index.jsx deleted file mode 100644 index 783d68cd8..000000000 --- a/src/app/components/pages/Index.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { APP_ICON } from 'app/client_config'; -import SvgImage from 'app/components/elements/SvgImage'; -import { translateHtml } from 'app/Translator'; - -export default class Index extends React.Component { - render() { - return ( -
    -
    - -
    -

    - {translateHtml( - 'APP_NAME_is_a_social_media_platform_where_everyone_gets_paid_for_creating_and_curating_content' - )}. -

    -
    -
    -
    - ); - } -} diff --git a/src/app/components/pages/Post.jsx b/src/app/components/pages/Post.jsx index d4060dc70..9ec51fcef 100644 --- a/src/app/components/pages/Post.jsx +++ b/src/app/components/pages/Post.jsx @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import tt from 'counterpart'; -import { SIGNUP_URL } from 'shared/constants'; import Comment from 'app/components/cards/Comment'; import PostFull from 'app/components/cards/PostFull'; import NotFoundMessage from 'app/components/cards/NotFoundMessage'; @@ -10,6 +9,7 @@ import { parseJsonTags } from 'app/utils/StateFunctions'; import { sortComments } from 'app/components/cards/Comment'; import DropdownMenu from 'app/components/elements/DropdownMenu'; import { serverApiRecordEvent } from 'app/utils/ServerApiClient'; +import { HIVE_SIGNUP_URL, SIGNUP_URL } from 'shared/constants'; import GptAd from 'app/components/elements/GptAd'; import ReviveAd from 'app/components/elements/ReviveAd'; import { isLoggedIn } from 'app/utils/UserUtil'; @@ -31,6 +31,7 @@ class Post extends React.Component { content: PropTypes.object.isRequired, dis: PropTypes.object, sortOrder: PropTypes.string, + scotTokenSymbol: PropTypes.string, loading: PropTypes.bool, }; constructor() { @@ -38,10 +39,6 @@ class Post extends React.Component { this.state = { showNegativeComments: false, }; - this.showSignUp = () => { - serverApiRecordEvent('SignUp', 'Post Promo'); - window.location = SIGNUP_URL; - }; } toggleNegativeReplies = e => { @@ -51,6 +48,10 @@ class Post extends React.Component { e.preventDefault(); }; + showSignUp = () => { + window.location = this.props.preferHive ? HIVE_SIGNUP_URL : SIGNUP_URL; + }; + onHideComment = () => { this.setState({ commentHidden: true }); }; @@ -61,7 +62,15 @@ class Post extends React.Component { render() { const { showSignUp } = this; - const { content, sortOrder, post, dis, loading } = this.props; + const { + content, + sortOrder, + appDomain, + scotTokenSymbol, + post, + dis, + loading, + } = this.props; const { showNegativeComments, commentHidden, showAnyway } = this.state; if (!content) { @@ -114,7 +123,7 @@ class Post extends React.Component { .toJS() .filter(c => content.get(c)); - sortComments(content, replies, sortOrder); + sortComments(content, replies, sortOrder, scotTokenSymbol); // Don't render too many comments on server-side const commentLimit = 100; @@ -284,6 +293,11 @@ export default connect((state, ownProps) => { sortOrder: currLocation.query.sort || 'trending', gptEnabled: state.app.getIn(['googleAds', 'gptEnabled']), reviveEnabled: state.app.get('reviveEnabled'), + scotTokenSymbol: state.app.getIn([ + 'hostConfig', + 'LIQUID_TOKEN_UPPERCASE', + ]), + appDomain: state.app.getIn(['hostConfig', 'APP_DOMAIN']), loading: state.app.get('loading'), }; })(Post); diff --git a/src/app/components/pages/PostsIndex.jsx b/src/app/components/pages/PostsIndex.jsx index 425ccfa0f..871e260b1 100644 --- a/src/app/components/pages/PostsIndex.jsx +++ b/src/app/components/pages/PostsIndex.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Link } from 'react-router'; import tt from 'counterpart'; -import { List, OrderedMap } from 'immutable'; +import { List, Map, OrderedMap } from 'immutable'; import { actions as fetchDataSagaActions } from 'app/redux/FetchDataSaga'; import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; import PostsList from 'app/components/cards/PostsList'; @@ -18,7 +18,6 @@ import SortOrder from 'app/components/elements/SortOrder'; import { PROMOTED_POST_PAD_SIZE } from 'shared/constants'; import tagHeaderMap from 'app/utils/TagFeedHeaderMap'; import MarkdownViewer from 'app/components/cards/MarkdownViewer'; -import { PREFER_HIVE, INTERLEAVE_PROMOTED, TAG_LIST } from 'app/client_config'; import { ifHivemind } from 'app/utils/Community'; import PostsIndexLayout from 'app/components/pages/PostsIndexLayout'; @@ -56,6 +55,7 @@ class PostsIndex extends React.Component { loading: PropTypes.bool, username: PropTypes.string, blogmode: PropTypes.bool, + interleavePromoted: PropTypes.bool, topics: PropTypes.object, }; @@ -95,13 +95,19 @@ class PostsIndex extends React.Component { } getPosts() { - const { order, pinned, posts, promotedPosts } = this.props; + const { + order, + pinned, + posts, + promotedPosts, + interleavePromoted, + } = this.props; const pinnedPosts = pinned ? pinned.has('pinned_posts') ? pinned.get('pinned_posts').toJS() : [] : []; - if (INTERLEAVE_PROMOTED && (order === 'trending' || order === 'hot')) { + if (interleavePromoted && (order === 'trending' || order === 'hot')) { let promotedDiscussions = promotedPosts; if (promotedDiscussions && promotedDiscussions.size > 0 && posts) { const processed = new Set( @@ -159,7 +165,7 @@ class PostsIndex extends React.Component { order, category, observer: username, - useHive: PREFER_HIVE, + useHive: this.props.preferHive, }); } @@ -391,6 +397,8 @@ module.exports = { path: ':order(/:category)', component: connect( (state, ownProps) => { + const hostConfig = state.app.get('hostConfig', Map()); + const preferHive = hostConfig.get('PREFER_HIVE'); // route can be e.g. trending/food (order/category); // or, @username/feed (category/order). Branch on presence of `@`. const route = ownProps.routeParams; @@ -403,7 +411,7 @@ module.exports = { : route.category ? route.category.toLowerCase() : null; const order = account_name ? route.category - : route.order || 'trending'; + : route.order || (hostConfig.get('DEFAULT_URL', '/trending').split('/')[1]); const hive = ifHivemind(category); const community = state.global.getIn(['community', hive], null); @@ -449,6 +457,11 @@ module.exports = { pinned: state.offchain.get('pinned_posts'), isBrowser: process.env.BROWSER, gptEnabled: state.app.getIn(['googleAds', 'gptEnabled']), + interleavePromoted: hostConfig.get( + 'INTERLEAVE_PROMOTED', + false + ), + preferHive, enableAds, }; }, diff --git a/src/app/components/pages/PostsIndex.scss b/src/app/components/pages/PostsIndex.scss index 9233e0961..057a3f901 100644 --- a/src/app/components/pages/PostsIndex.scss +++ b/src/app/components/pages/PostsIndex.scss @@ -278,7 +278,9 @@ } } &__summary.promoted { - border: 1px solid $color-teal; + @include themify($themes) { + border: 1px solid themed('colorAccent'); + } padding-top: 5px; padding-left: 5px; } @@ -726,7 +728,9 @@ a#changeLayout:focus { } } &__summary.promoted { - border: 1px solid $color-teal; + @include themify($themes) { + border: 1px solid themed('colorAccent'); + } padding-top: 5px; padding-left: 5px; } diff --git a/src/app/components/pages/PostsIndexLayout.jsx b/src/app/components/pages/PostsIndexLayout.jsx index 03c8e0d72..2ed51bceb 100644 --- a/src/app/components/pages/PostsIndexLayout.jsx +++ b/src/app/components/pages/PostsIndexLayout.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { List } from 'immutable'; +import { List, Map } from 'immutable'; import { actions as fetchDataSagaActions } from 'app/redux/FetchDataSaga'; import SidebarLinks from 'app/components/elements/SidebarLinks'; import SidebarNewUsers from 'app/components/elements/SidebarNewUsers'; @@ -13,7 +13,6 @@ import CommunityPane from 'app/components/elements/CommunityPane'; import CommunityPaneMobile from 'app/components/elements/CommunityPaneMobile'; import ReviveAd from 'app/components/elements/ReviveAd'; import SidebarToken from 'app/components/elements/SidebarToken'; -import { HIVE_ENGINE, SHOW_TOKEN_STATS } from 'app/client_config'; class PostsIndexLayout extends React.Component { static propTypes = { @@ -47,6 +46,7 @@ class PostsIndexLayout extends React.Component { blogmode, isBrowser, children, + scotTokenSymbol, } = this.props; const mqLarge = @@ -66,10 +66,14 @@ class PostsIndexLayout extends React.Component { {isBrowser && !community && username && ( - + )} - {SHOW_TOKEN_STATS && + {this.props.showTokenStats && this.props.isBrowser && this.props.tokenStats && (
    @@ -78,25 +82,21 @@ class PostsIndexLayout extends React.Component { 'scotToken', ])} scotTokenCirculating={this.props.tokenStats.getIn( - [ - 'total_token_balance', - 'circulatingSupply', - ] + ['total_token_balance_circulating'] )} scotTokenBurn={ this.props.tokenStats.getIn([ 'token_burn_balance', - 'balance', ]) || 0 } scotTokenStaking={this.props.tokenStats.getIn( - ['total_token_balance', 'totalStaked'] + ['total_token_balance_staked'] )} useHive={this.props.hiveEngine} />
    )} - {SHOW_TOKEN_STATS && + {this.props.showTokenStats && this.props.isBrowser && this.props.tokenStats && this.props.tokenStats.getIn(['scotMinerTokens', 0]) && ( @@ -108,27 +108,23 @@ class PostsIndexLayout extends React.Component { ])} scotTokenCirculating={this.props.tokenStats.getIn( [ - 'total_token_miner_balance', - 'circulatingSupply', + 'total_token_miner_balance_circulating', ] )} scotTokenBurn={ this.props.tokenStats.getIn([ - 'token_miner_burn_balance', + 'token_burn_miner_balance', 'balance', ]) || 0 } scotTokenStaking={this.props.tokenStats.getIn( - [ - 'total_token_miner_balance', - 'totalStaked', - ] + ['total_token_miner_balance_staked'] )} useHive={this.props.hiveEngine} />
    )} - {SHOW_TOKEN_STATS && + {this.props.showTokenStats && this.props.isBrowser && this.props.tokenStats && this.props.tokenStats.getIn(['scotMinerTokens', 1]) && ( @@ -140,20 +136,17 @@ class PostsIndexLayout extends React.Component { ])} scotTokenCirculating={this.props.tokenStats.getIn( [ - 'total_token_mega_miner_balance', - 'circulatingSupply', + 'total_token_mega_miner_balance_circulating', ] )} scotTokenBurn={ this.props.tokenStats.getIn([ - 'token_mega_miner_burn_balance', - 'balance', + 'token_burn_mega_miner_balance', ]) || 0 } scotTokenStaking={this.props.tokenStats.getIn( [ - 'total_token_mega_miner_balance', - 'totalStaked', + 'total_token_mega_miner_balance_staked', ] )} useHive={this.props.hiveEngine} @@ -190,8 +183,10 @@ class PostsIndexLayout extends React.Component { export default connect( (state, props) => { + const hostConfig = state.app.get('hostConfig', Map()); + const scotTokenSymbol = hostConfig.get('LIQUID_TOKEN_UPPERCASE'); const scotConfig = state.app.get('scotConfig'); - const hiveEngine = HIVE_ENGINE; + const hiveEngine = hostConfig.get('HIVE_ENGINE'); const username = state.user.getIn(['current', 'username']) || state.offchain.get('account'); @@ -207,7 +202,13 @@ export default connect( topics: state.global.getIn(['topics'], List()), isBrowser: process.env.BROWSER, username, - tokenStats: scotConfig.getIn(['config', 'tokenStats']), + interleavePromoted: hostConfig.get('INTERLEAVE_PROMOTED', false), + scotTokenSymbol, + tokenStats: scotConfig.getIn([ + 'config', + 'hiveTokenStats', + ]), + showTokenStats: hostConfig.get('SHOW_TOKEN_STATS', true), hiveEngine, reviveEnabled, }; diff --git a/src/app/components/pages/SearchIndex.jsx b/src/app/components/pages/SearchIndex.jsx index 87868187a..18c0a5d65 100644 --- a/src/app/components/pages/SearchIndex.jsx +++ b/src/app/components/pages/SearchIndex.jsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import tt from 'counterpart'; import { search } from 'app/redux/SearchReducer'; -import Callout from 'app/components/elements/Callout'; import ElasticSearchInput from 'app/components/elements/ElasticSearchInput'; import PostsList from 'app/components/cards/PostsList'; import { List, Map, fromJS } from 'immutable'; @@ -94,11 +93,6 @@ class SearchIndex extends React.Component { />
    - {!loading && result.length === 0 ? ( - {'Nothing was found.'} - ) : ( - searchResults - )}
    ); diff --git a/src/app/components/pages/Support.jsx b/src/app/components/pages/Support.jsx index 23c1a26a2..58accf0d0 100644 --- a/src/app/components/pages/Support.jsx +++ b/src/app/components/pages/Support.jsx @@ -1,20 +1,11 @@ import React from 'react'; import tt from 'counterpart'; -import { APP_NAME } from 'app/client_config'; class Support extends React.Component { render() { return (
    - +
    ); } diff --git a/src/app/components/pages/Topics.jsx b/src/app/components/pages/Topics.jsx index 3656ccbce..be5cbb5b4 100644 --- a/src/app/components/pages/Topics.jsx +++ b/src/app/components/pages/Topics.jsx @@ -7,7 +7,7 @@ import PropTypes from 'prop-types'; import NativeSelect from 'app/components/elements/NativeSelect'; import { actions as fetchDataSagaActions } from 'app/redux/FetchDataSaga'; import { ifHivemind } from 'app/utils/Community'; -import { List } from 'immutable'; +import { List, Map } from 'immutable'; const buildPrefix = level => { let a = ''; @@ -74,13 +74,13 @@ class Topics extends Component { browserHistory.push(selectedOption.value); }; - currentlySelected = (currentTag, username, currentOrder = false) => { + currentlySelected = (currentTag, username, currentOrder = false, defaultUrl = '/trending') => { const opts = { feed: `/@${username}/feed`, tagOnly: `/trending/${currentTag}`, orderOnly: `/${currentOrder}`, tagWithOrder: `/${currentOrder}/${currentTag}`, - default: `/trending`, + default: defaultUrl, }; if (currentOrder === 'feed') return opts['feed']; if (currentTag && currentOrder) return opts['tagWithOrder']; @@ -99,15 +99,17 @@ class Topics extends Component { communities, categories, communityMap, + hostConfig, } = this.props; const currentOrder = this.props.order; const order = currentOrder == 'feed' ? 'trending' : currentOrder; + const defaultUrl = hostConfig.get('DEFAULT_URL', '/trending'); if (compact) { const extras = username => { const ex = { allTags: order => ({ - value: `/${order}`, + value: currentOrder == 'feed' ? defaultUrl : `/${order}`, label: `${tt('g.all_tags_mobile')}`, }), myFeed: name => ({ @@ -123,7 +125,7 @@ class Topics extends Component { categories .map(cat => { const { tag, label } = parseCategory(cat, communityMap); - + const link = order ? `/${order}/${tag}` : `/${tag}`; return { value: link, label: label }; }) @@ -134,7 +136,8 @@ class Topics extends Component { currentlySelected={this.currentlySelected( current, username, - currentOrder + currentOrder, + defaultUrl )} options={opts} onChange={this.handleChange} @@ -242,7 +245,115 @@ class Topics extends Component {
    - {categoriesLinks} + {/* Added Static Tags */} +
  • + + Art + +
  • +
  • + + Buidl + +
  • +
  • + + Build-it + +
  • +
  • + + Crafts + +
  • +
  • + + Crocheting + +
  • +
  • + + DIY español + +
  • +
  • + + Handmade + +
  • +
  • + + Home Improvement + +
  • +
  • + + Homesteading + +
  • +
  • + + Howto + +
  • +
  • + + Tutorial + +
  • +
  • + + Social Ecology + +
  • @@ -325,11 +436,13 @@ export default connect( communityMap[tag] = state.global.getIn(['community', ifHivemind(tag), 'title'], null); } }); + const hostConfig = state.app.get('hostConfig', Map()); return { ...ownProps, communities: state.global.get('community'), categories, communityMap, + hostConfig, }; }, dispatch => ({ diff --git a/src/app/components/pages/UserProfile.jsx b/src/app/components/pages/UserProfile.jsx index 3f2cea268..524b92ba9 100644 --- a/src/app/components/pages/UserProfile.jsx +++ b/src/app/components/pages/UserProfile.jsx @@ -49,8 +49,6 @@ const emptyPostsText = (section, account, isMyAccount) => {
    Trending Articles
    - Welcome Guide -
    {/* TODO: introduceyourself nudge, FUUX tt('user_profile.read_the_quick_start_guide') @@ -117,7 +115,7 @@ export default class UserProfile extends React.Component { order, category, observer: username, - useHive: PREFER_HIVE, + useHive: this.props.preferHive, }); } @@ -328,6 +326,7 @@ module.exports = { const username = state.user.getIn(['current', 'username']); const accountname = ownProps.routeParams.accountname.toLowerCase(); const walletUrl = state.app.get('walletUrl'); + const preferHive = state.app.getIn(['hostConfig', 'PREFER_HIVE']); let { section } = ownProps.routeParams; if (!section) section = 'blog'; @@ -370,6 +369,7 @@ module.exports = { blogmode: state.app.getIn(['user_preferences', 'blogmode']), profile: state.userProfiles.getIn(['profiles', accountname]), walletUrl: walletUrl + '/@' + accountname + '/transfers', + preferHive, section, order, category: '@' + accountname, diff --git a/src/app/help/en/faq.md b/src/app/help/en/faq.md index 58dac5ab7..31d289bd1 100644 --- a/src/app/help/en/faq.md +++ b/src/app/help/en/faq.md @@ -1,1118 +1,228 @@ -# Hive.blog FAQ +# Build-it FAQ ## Table of Contents ### General -- What is Hive.blog? -- How does Hive.blog work? -- How does Hive.blog differ from other social media websites? -- What do I need to do in order to secure my account? -- Does it cost anything to post, comment, or vote? -- Can I earn digital tokens on Hive? How? -- Where do the tokens come from? -- Where does the value come from? -- Why are people getting vastly different rewards? - -### Accounts -- Can I change my username? +- What is Build-it? +- How does Build-it work? +- What is the BUIDL token? +- What does a Blockchain Technology mean and the benefits? +- What makes Build-it different and unique from other social media websites? +- What are cryptocurrencies? +- How do I become an eligible member of the community? + +### Account creation +- How do I sign up for an account? +- Can I recover my account if it got compromised? - Can I delete or deactivate my account? - -### Community -- Am I required to verify my identity? - -### Site Navigation -- How do I upvote a post or comment? -- What do the Home, New, Hot, Trending, and Promoted links show? -- What information is available in my account menu? -- How do I see my recent rewards? -- What information is shown in my wallet? -- How do I transfer my HIVE or Hive Dollars into savings? -- How do I send money to another user? -- Will I receive notifications when there is activity with my account? -- What is shown in my profile? -- How do I change my avatar image and other profile information? -- What is the recommend size for the cover image? -- How can I control whether I see "Not Safe For Work" (NSFW) content? -- How do I search for content? -- Can I see which users I have muted? -- Can I see which users have muted me? -- Can I see the list of users I am following, and who is following me? -- What languages are supported? - -### Posting -- What can users post to Hive.blog? -- What are the different choices for post rewards (50%/50%, Power Up 100%, Decline Payout)? -- How do I add images and photos to my posts? -- How do I set the thumbnail image for my post? -- What is the recommend aspect ratio for thumbnail images? -- How do I add videos to my posts? -- Is there a way I can make my images smaller? -- What are tags? -- What tags should I use? -- How many tags can I use? -- Why is the "Post" button grayed out? -- How do I format text in Markdown? -- How often can I post? -- How long can my post be? -- If posting in a language other than English, how will I get recognized? -- Can I delete something I posted? -- What does "Promoting" a post do? -- How do I promote a post? - -### Comments -- Can I earn digital tokens for commenting? -- How often can I comment? +- What do I need to do in an attempt to keep my account secured? ### Economics -- Where do the new HIVE tokens come from? -- How many new tokens are generated by the blockchain? -- How are the new tokens distributed? -- Which exchanges are HIVE and HBD listed on? -- What is the reward pool? -- How is the reward pool split between authors and curators? -- Will the reward pool pay out more or less depending on who votes? -- Why do the earnings for my post go up or down? -- When can I claim my rewards? -- What is the difference between HIVE, HIVE Power, and Hive Dollars? -- What is delegated HIVE Power? -- What determines the price of HIVE? -- How do I get more HIVE Power? -- How long does it take HIVE or HIVE Power that I purchased to show up in my account? -- What is powering up and down? -- What do the dollar amounts for pending payouts represent? -- Will 1 Hive Dollar always be worth $1.00 USD? -- How do Hive Dollar to HIVE conversions work? -- Is there a way for me to convert my Hive Dollars to HIVE without waiting 3.5 days? -- What can I do with my HIVE tokens? -- What can I do with my HBD tokens? -- What is a MVEST? -- Can I sell goods and services on Hive? -- How can I withdraw my HIVE or HBD coins? -- Will I get a 1099 from Hive.blog? -- How much are the transaction fees for sending tokens to other users? -- Are there fees for Powering Up, Powering Down, trading on the internal market, or converting HBD to HIVE? -- How long does it take to transfer HIVE or HBD tokens between users? - -### Voting and Curating -- What is my voting mana? -- How many times can I vote without depleting my voting mana? -- Can I vote with less than 100% of my voting strength? -- Where can I check my voting mana? -- What determines how much of the curation reward goes to the author versus curators? -- Can I get curation rewards for upvoting comments? -- Do I get curation rewards for downvoting posts or comments? -- What are curation trails? -- Why don't my upvotes have an effect on a post's rewards? -- Is there a way to make my votes count for more? -- What are the valid reasons for downvoting? -- Does a downvote mean that I did something wrong? -- Will a downvote hurt my reputation? - -### Plagiarism, Spam, and Abuse -- What is considered spam or abuse? -- What are Hive.blog’s policies on plagiarism? -- Is it okay to use random pictures from the internet? -- What is Hivewatchers? -- What is @cheetah? -- Where do I report a post or comment that contains plagiarism, spam, or abuse? - -### Reputation -- What is Reputation? -- How is the Reputation score measured? -- How do I improve my reputation score? -- What causes my reputation score to go down? -- Why does my reputation score matter? - -### Followers, Feeds, and Reblog -- What is Reblogging? -- Can I share on other social media? - -### Blockchain -- What is a blockchain? -- What is the Hive blockchain? -- How do Resource Credits work on the Hive blockchain? -- What is the difference between Hive and Hive.blog? -- How is Hive different from Bitcoin? -- What is the difference between Proof of Work, Proof of Stake, and Delegated Proof of Stake? -- How often does the Hive blockchain produce a new block? -- Is there a way to see the raw data that is stored in the blockchain? -- Where can I find the information for the official launch of the blockchain? -- Can I mine HIVE? - -### Security -- How can I keep my Hive account secure? -- Why should I be careful with my master password? -- Why is the master password a long string of gibberish? -- What are my different keys for? -- What do I do if I lost my password/keys? -- Are my HIVE and Hive Dollar tokens insured in the event of a hack or if someone takes over my account? -- How do I report a security vulnerability? - -### Developers -- Are the Hive blockchain and Hive.blog code open-source? -- Is there a Github page for Hive.blog? -- Is there a Github page for the Hive blockchain? -- What is available for developers interested in Hive and Hive.blog? -- How do I use cli_wallet? - -### Witnesses -- What are Hive witnesses? -- How can I vote for witnesses? -- How many witnesses can I vote for? - -### Miscellaneous -- Where can I ask for help if my question was not answered here? - -### Disclaimer -- Third Party References and User Links +- Where do the new BUIDL token come from? +- What is the Reward Pool? +- How do I get more BUIDL tokens? +- So I earn digital tokens on my blog, how is that even possible? +- What do I do with the tokens? # General -## What is hive.blog? +## What is Build-it? -Hive has redefined social media by building a living, breathing, and growing social economy - a community where users are rewarded for sharing their voice. It's a new kind of attention economy. +Build-it is a free speech socials sharing where people can come to learn, share, and +earn for sharing their experiences of doing, building, and fixing things on their own. +This includes guides to; roofing, carpentry, woodwork, brickwork/stonework,crocheting, recycling, drawing/painting, gardening, tiling and much more! Literally,if you want to improve your DIY skills, this is the best community to start with. ^ -## How does Hive.blog work? - -Hive.blog is one of the many websites that are powered by the Hive blockchain and HIVE cryptocurrency. All of these websites read and write content to the Hive blockchain, which stores the content in an immutable blockchain ledger, and rewards users for their contributions with digital tokens called HIVE. - -Every day, the Hive blockchain mints new HIVE tokens and adds them to a community's "rewards pool". These tokens are then awarded to users for their contributions, based on the votes that their content receives. Users who hold more tokens in their account as "Hive Power" will get to decide where a larger portion of the rewards pool is distributed. - -^ -## How does Hive.blog differ from other social media websites? - -While most social media sites extract this value for the benefit of their shareholders, Hive believes that the users of the platform should receive the benefits and rewards for their attention and the contributions they make to the platform. - -^ -## What do I need to do in order to secure my account? - -Unlike most social media websites, **there is no way to recover your account if you lose your password / owner key!** This is why it is extremely important that you save and backup your password somewhere safe. It is strongly recommended that you store an offline copy in case of a hard drive failure or other calamity. Consider digital offline storage, such as an external disk or flash drive, as well as printed paper stored in a fireproof safe. Use a safe deposit box for best redundancy. - -If you leak your private key to another user or a third-party website, that user or website will have full access to your account. This means they can steal it, as well as the funds inside of it. It is therefore not recommended to enter your private key information with any other user or third-party website. If you believe your account has been compromised, you should change your password/keys right away. - -Each account has multiple keys, which each have different levels of authority: owner, active, posting, and memo. The information on the different types of keys and their purposes, as well as more information on password and key security, can be found in the section on Security. +## How does Build-it work? +By creating DIY contents on [Build-it](https://www.build-it.io/@build-it/feed), +creators earn a cryptocurrency called $buidl rewards publishing DIY related contents. +These contents are stored on [Hive Blockchain](https://hive.io/) - for security and +censorship-resistant. Project owners can build their followers and connect smartly +with their fanbase. ^ -## Does it cost anything to post, comment, or vote? - -No. It is free to post, comment, and vote on content on hive.blog. You might even get paid for it! +## What is the BUIDL token? +The $BUIDL token is the utility token used inside the Build-it network. It’s a digital +asset that gives users ownership rights and power to influence others either by +upvotes or tips. There will lots of usecase tied around the $BUIDL token upon which +users can spend their tokens on things they love. ^ -## Can I earn digital tokens on Hive? How? +## What does a Blockchain Technology mean and the benefits? -You can earn digital tokens on Hive by: +A blockchain is a list of records (blocks) upon which data transactions are stored in +a secured,tamper-proof, unalterable and transparent manner. Blockchain’s unique characteristics solves many business issues in our world today. +Below are some benefits of utilization a blockchain technology in your business or +projects: -**Posting** - By sharing your posts, you can earn upvotes from community members. Depending on the upvotes you receive, you may get a portion of the "rewards pool". +- Enhanced security: Transactions must be agreed upon before they are recorded on +the decentralized ledger. Right after a transaction is approved, it’s automatically +encrypted and linked to the previous transaction. Also, the fact that information is +stored across a network of computers other then a single server, makes it very +difficult and impossible for a hacker to compromise the transaction data. -**Voting and curating** - If you discover a post and upvote it before it becomes popular, you can earn a curation reward. The reward amount will depend on the amount of Hive Power you have. +- Improved transparency: Transaction histories are becoming more transparent +through the use of Blockchain technology. Since it is a distributed ledger, all network +participants share the same documentation as opposed to individual copies. -**Purchasing** - Users can purchase Hive or Hive Dollar tokens directly through their Hive wallet using bitcoin, Ether, or BitShares tokens. They are also available from other markets and exchanges including [BlockTrades](https://blocktrades.us) and [Ionomy](https://ionomy.com). - -**Vesting** - HIVE tokens that are powered up to Hive Power will earn a small amount of new tokens for holding. - -^ -## Where do the tokens come from? +- Increased performance and speed: Unlike our centralized traditional entities that -The Hive network continually creates new digital tokens to reward content creators and curators. Some of the newly-created tokens are transferred to users who add value to Hive by posting, commenting, and voting on other people's posts. The remainder is distributed to holders of Hive Power and the witnesses that power the blockchain. +utilize paper-heavy process which consumes a significant amount of time and are +prone to human error. By streamlining and automating these processes with +blockchain,transactions are lighting fast and efficient. ^ -## Where does the value come from? +## What makes Build-it different and unique from other social media websites? -At its root, Hive is simply a points system. However, because this points system is blockchain-based, the points can be traded on markets as tokens. People buy and sell these tokens, and many hold in anticipation of increased purchasing power for various Hive-related services. - -By analogy, Hive is a game system where users compete for attention and rewards by bringing content and adding value to the platform. The rewards people earn are tokens that have market value and are readily tradable. It is similar to how someone playing a video game could obtain a limited item or currency by playing the game. If the currency or items are transferable between users, then they can sell or buy them on game item markets. +While most social media websites collects and sells users data and info without their +consent,build-it stores this data in an encrypted ledger known as Blockchain +Technology, it also shares the rewards to users (in the form of a cryptocurrency +called $buidl) for adding value and content to the network respectively. ^ -## Why are people getting vastly different rewards? - -Hive is not a "get rich quick" scheme. While it is possible to post content that goes viral quickly and earn a lot of rewards on a single post, this is not typical for most users. - -Most of the authors that you see earning high rewards are users that have spent a lot of time in the network building followings, making connections with others, and developing a reputation for bringing high quality content. - -It is best to have realistic expectations, without focusing on rewards when you are first starting out. Work on building a following, making connections, and developing a good reputation. Consistency will pay off in the long run. +## What are cryptocurrencies? + +Simply put, a +[cryptocurrency](https://www.investopedia.com/terms/c/cryptocurrency.asp) is a +new form of digital money. Just like our traditional currency (i.e US dollar) that can +be transferred and used to buy products and services the same is applicable for +cryptocurrencies. +They are virtual currencies that can be used to pay for a product or services based on +a network. Unlike our traditional currencies +, +cryptocurrencies are secured by a +cryptography which makes it impossible to counterfeit or double-spend. ^ +## How do I become an eligible member of the community? -# Accounts - -## Can I change my username? - -Account names can not be changed. If you would like a new account name, you must pay to create a new account using a third-party account creation service. - -^ -## Can I delete or deactivate my account? - -Accounts can not be deactivated or deleted. The account along with all of its activity is permanently stored in the blockchain. - -^ -# Community - -## Am I required to verify my identity? - -Verification is a process where users give evidence to show that they are the person that they claim to be. This is to reduce fraud and people impersonating known figures. If you would like to remain anonymous, that is perfectly fine. However if you claim to be someone specific, the community may expect that you verify you are who you say you are. - -There are a number of ways to do this. The most common way to verify your identity is by posting a link to your Hive profile on a website or social media account which you are already known for having such as Twitter, Facebook, LinkedIn, a blog, or photography site. - -Many users also like to post a photo or a video which shows them holding up a sheet of paper with the current date and their Hive account name handwritten on it. This is a great way to add a personal touch to verifying. - -^ -# Site Navigation - -## How do I upvote a post or comment? - -To upvote a post or comment, click on the "upvote" icon at the bottom of the post/comment. - -^ -## What do the Home, New, Hot, Trending, and Promoted links show? - -These are various ways to sort Hive posts. - -**Home** - The most recent posts of the accounts you follow (your feed). - -**New** - Posts are sorted by the time posted, with newest first. - -**Hot** - Popular posts at the moment. - -**Trending** - Posts with the most amount of votes, stake-weighted, recently. - -**Promoted** - Listings that are boosted by Hive Dollar payments get promoted for greater visibility. - -^ -## What information is available in my account menu? - -You can get to your account menu by clicking on the avatar icon in the top-right corner of a hive.blog page. - -**Feed** - Here is where you go to see the most recent posts from the people you follow. - -**Blog** - Here is where you go to see all of your posts and reblogs. It is also where you go to see your profile page that is viewable by other users. - -**Comments** - Here is where you go to see all of the comments and replies you have made. - -**Replies** - Here is where you go to see all replies other users have made to your posts and comments. - -**Toggle Night Mode** - This will toggle the hive.blog website views between "night" and "day" mode themes. - -**Wallet** - This will take you to your wallet, where you go to see your wallet balances, make transfers, exchange HIVE or Hive Dollars, manage your account, and Power Up. - -**Settings** - Here is where you go to update your settings. - -**Logout** - If you'd like to logout. - -^ -## How do I see my recent rewards? - -The Rewards drop-down menu is available in your wallet. Click it and there are two links: - -**Curation rewards** - Shows the rewards earned for upvoting posts and comments. - -**Author rewards** - Shows the rewards earned by your own posts and comments. - -You can also view the same information for other users by visiting their wallet profile. - -^ -## What information is shown in my wallet? - -Your wallet shows how many Hive and Hive Dollar tokens you have in your account. It shows how much Hive Power it has, and how much HP is delegated. It also shows how many of your HIVE and Hive Dollar tokens are being held in the savings account, which is a balance that is subject to 3 day withdraw waiting period. The wallet page shows any the progress of any Hive Dollar to HIVE conversions as well as the status of a power down. It also shows an estimated value of all the tokens in your account, based on the recent market price of HIVE. - -^ -## How do I transfer my HIVE or HIVE Dollars into savings? - -Your savings balance is HIVE and HBD tokens that are subject to 3 day withdraw waiting period. This is an extra security measure in case your account credentials are compromised. To transfer HIVE or HBD tokens into savings, click on the drop-down arrow next to HIVE or HIVE DOLLARS in your wallet, and select "Transfer to Savings". - -^ -## How do I send money to another user? - -- From your wallet page, click the HIVE or Hive Dollar balances with the down arrow next to them. -- In the drop-down menu, click 'Transfer'. -- Type the username of the account you want to send the HIVE or Hive Dollars to. Double and triple check the spelling. -- Enter the amount of HIVE or Hive Dollars to send. -- Enter a memo to go along with the transaction (optional). -- Click Submit. -- You will be prompted for your password. You will need to enter your master password or active key. - -^ -## Will I receive notifications when there is activity with my account? - -Notifications are currently not supported, but they are a feature we are planning to add. - -^ -## What is shown in my profile? - -At the top of your profile is your display name and reputation score. Below your display name is the number of followers you have, the number of posts and comments you have written, and the number of people you are following. It also shows the month and year when your account was created. - -You have the option to change your avatar and display name on the [wallet.hive.blog](https://wallet.hive.blog) Settings page. There, you can set additional information such as "about" information, your location, and add a link to a website of your choosing. You also have the option to set a cover image for your profile. - -You can view your own profile by clicking on the link to your Blog in your account menu. - -^ -## How do I change my avatar image and other profile information? - -Your profile info, avatar image, and cover image are set in your [wallet.hive.blog](https://wallet.hive.blog) Settings page. In order to update your avatar picture and cover image, you will need to host the images somewhere. This can be done by uploading it via the wallet GUI, or using a third-party image hosting site such as Postimage. Once your image is uploaded, copy its URL and paste it into the "Profile Picture URL" box for the avatar, or the "Cover Image URL" box for the cover image. Then click the Update button and enter your password or active key. - -^ -## What is the recommend size for the cover image? - -The cover image will be resized/scaled depending on the device being used. Therefore it is recommend to use an image that will still look good when cropped or resized. A 2048x512 image is the optimal size to work for most devices. - -^ -## How can I control whether I see "Not Safe For Work" (NSFW) content? - -By default, content that users have tagged as "NSFW" will be hidden, but a link will be shown to reveal the content. - -You can update your display preference with the Settings page so that NSFW content is always shown by default, remains hidden until clicked, or is completely hidden with no option to reveal. - -^ -## How do I search for content? - -In the upper right corner of Hive.blog, there is a magnifying glass search link where you can find posts using a keyword search. - -There is also an **Explore** link in the main menu, where you can browse through posts based on tags. +It takes barely 5 minutes to become a verified member of our friendly community. +To become a verified member of the network take these 4 points into consideration: -^ -## Can I see which users I have muted? +- Sign up for a new account (if you have an existing account, please ignore this +point). -Yes. This can be seen under the Settings page. +- Upon sighing, join at least one of our friendly socials and get to know members of +the communities. -^ -## Can I see which users have muted me? +- Get some $buidl tokens. Being a stake holder of the token also makes you a -This information is not presented on hive.blog, but it is visible on the blockchain level. +shareholder of revenue generated from the network. -^ -## Can I see the list of users I am following, and who is following me? +iv. Be friendly :) -Yes. You can see the list of followers or people you are following by clicking on the links on your profile page. -^ -## What languages are supported? +# Account Creation -Currently hive.blog supports English, Spanish, Russian, French, Italian, Korean, Polish, and Chinese. There are also many communities using the platform that speak other languages. If you are interested in joining our translation team to add additional languages or help support the ones we have, please email us at translate@hive.io. +## How do I sign up for an account? -^ -# Posting +Just the same way we create our Facebook and Instagram account, same is +applicable here: -## What can users post to Hive.blog? +- User name -Hive is an open platform meant to host and welcome any legal content that complies with our [terms of service](/tos.html). Users can post anything they want, whether it be phrases, quotes, blogs, anecdotes, photos, videos, memes, songs, and more. Be creative! +- Existing Email address -^ -## What are the different choices for post rewards (50%/50%, Power Up 100%, Decline Payout)? +- Back up your password/keys. -- **50%/50%** - This rewards in half Hive Power, and half liquid HIVE / Hive Dollars. The ratio of liquid HIVE to Hive Dollars rewarded is based on network conditions at the time of payout. This is the default payout option. +For extra security the keys “Alphanumeric” in nature. Remember your data and +information remains on the Blockchain. -- **Power Up 100%** - This option rewards the post in 100% Hive Power. - -- **Decline Payout** - Use this option to receive no post rewards. Votes will affect the post's position on the trending ranking but no rewards are paid from Hive's reward pool. Replies made to the post are still eligible for rewards. - -^ -## How do I add images and photos to my posts? - -You can browse your hard drive to add an image by clicking on the "selecting them" link from within the editor. - -If you have an image copied to your clipboard, you can simply paste (`ctrl + v`) while in the post/comment editor, and your image will be uploaded into your post or comment. Due to the file size of these pasted images, this method is only recommended for simple graphics. Photos (.JPG) should be uploaded from your disk. - -Pictures can also be hosted on an external site. Paste the image's web address (URL) into the editor and it will automatically be added. - -^ -## How do I set the thumbnail image for my post? - -The first image in the post will automatically be set as the thumbnail image. - -^ -## What is the recommend aspect ratio for thumbnail images? - -The recommend aspect ratio for thumbnail images is 16x9. - -^ -## How do I add videos to my posts? - -To add a DTube, YouTube, or Vimeo video to your blog post, simply paste the URL link to the video into the post. - -^ -## Is there a way I can make my images smaller? - -Yes, but the picture must be resized before it is uploaded into the hive.blog editor. This can be done in your favorite photo editing software, or online by uploading to a third-party website that features editing such as imgur.com. - -^ -## What are tags? - -Tags are a way to categorize your content, so that others can find it. The more relevant the tags are to the post, the more like-minded people will come across it. - -^ -## What tags should I use? - -Try to use tags that are relevant to your post, and that will be popular for other people to browse. For example, "mytriptoalaska" may be relevant to your post, but readers are probably not going to go searching for that. Using "travel" would be a better choice for a tag in this case. - -You can browse through commonly used tags using the "Explore" link, in the main menu. - -Be mindful when choosing tags. If your tags aren’t related to your post, your post may get downvotes for mistagging. - -All tags must be lowercase letters. Spaces aren't allowed, but hyphenated words with a single dash are. - -^ -## How many tags can I use? - -You can use up to 8 tags per post. - -^ -## Why is the "Post" button grayed out? - -A post must have a title, body, and at least one valid tag. If any of these are missing, then the "Post" button will be disabled. - -^ -## How do I format text in Markdown? - -Some common markdown syntax is: -- `**bold**` **bold** -- `_italics_` _italics_ -- `~~cross out~~` ~~cross out~~ - -Text can be sized using headers: -``` -# H1 -## H2 -### H3 -#### H4 -``` -# H1 -## H2 -### H3 -#### H4 - -For more advanced formatting, a guide describing the common markdown formatting syntax can be found here: Markdown Cheatsheet. - -^ -## How often can I post? - -You are allowed to post almost as often as you like. Currently, posts must be spaced 5 minutes apart. However, the community may not find value in users that are posting too frequently. Keep in mind what your audience will be interested in viewing, so that you do not overwhelm your followers with too much content. - -^ -## How long can my post be? - -Post sizes are limited to about 64,000 characters including formatting. This is ample for most posts. If writing blogs, consider how much people are willing to read at one time. If you make your posts too long, readers may lose interest which may affect the amount of upvotes and rewards you receive. - -^ -## If posting in a language other than English, how will I get recognized? - -You can use language-specific tags to help you to reach the audience that speaks your language. - -Language-specific groups include: -- Chinese = cn -- German = deutsch -- Spanish = spanish -- Korean = kr -- Russian = ru -- French = fr -- Portuguese = pt - -^ -## Can I delete something I posted? - -The blockchain will always contain the full edit history of posts and comments, so it can never be completely deleted. If you would like to update a post so that users cannot see the content via hive.blog, you can edit the post and replace it with blank content for as long as the post is active. After seven days, the post can no longer be edited via hive.blog. +^ +## Can I recover my account if it got compromised? -^ -## What does "Promoting" a post do? +Unfortunately, you can’t recover your account if you loose your password or private +keys. This is because each account has real monetary value and as such, should be +protected at all cost. It is very crucial you safe your password and private keys +somewhere you trust, we recommend that you store in an offline copy all your +password and private keys smartly. -When you make a post, there is the option to promote it with Hive Dollars. It will then show up in the “Promoted” tab. The order that it appears in the list depends on how much the post was promoted for. Posts with a higher promoted amount will be higher than posts with less. +^ +## Can I delete or deactivate my account? -Hive Dollars spent to promote a post are paid to the account @null, which nobody owns or controls. Once a user transfers SBD to @null, the Hive blockchain removes them from the currency supply. +No. Accounts cannot be deleted or deactivated. Remember,all accounts are created +and stored on the blockchain. Due to the disruptive power of blockchain technology, +all transactions and records are stored on the blockchain. -You can promote your own posts, or posts that you like from other users. +In a nutshell. You can’t delete or deactivate your account. -^ -## How do I promote a post? +^ +## What do I need to do in an attempt to keep my account secured? -At the bottom of each post is a button to "Promote". After clicking the button, type the number of Hive Dollars that you want to spend and click “PROMOTE”. The operation will require your master password or active key. +Your password and private keys should be protected at all cost. +Upon logging into your account using the key with appropriate permissions for what +you are doing: -^ -# Comments +- Posting key for blogging interface i.e voting, upvoting, rebloging, commenting etc -## Can I earn digital tokens for commenting? +- Active key for **Wallet interface** when attempting a transfer, delegating, +withdrawal,and a few other transactions. -Yes, comments that are upvoted can earn rewards just like posts! +- Master keys and owner keys when changing the password. -^ -## How often can I comment? +Gentle reminder,always save all your keys and keep it safe. Remember not to share +your keys with projects not associated with Build-it, unless otherwise stated. -There is a three second wait time in between comments to limit spam. -^ +^ # Economics -## Where do the new HIVE tokens come from? - -Blockchains like Hive and Bitcoin produce new tokens each time a block is produced. Unlike Bitcoin, where all of the new coins go to the block producers (called miners), the Hive blockchain allocates a majority of the new tokens to a reward fund called the "rewards pool". The rewards pool gives users tokens for participating in the platform based on the value they add. - -^ -## How many new tokens are generated by the blockchain? - -Starting with the network's 16th hard fork in December 2016, Hive began creating new tokens at a yearly inflation rate of 9.5%. The inflation rate decreases at a rate of 0.01% every 250,000 blocks, or about 0.5% per year. The inflation will continue decreasing at this pace until the overall inflation rate reaches 0.95%. This will take about 20.5 years from the time hard fork 16 went into effect. - -^ -## How are the new tokens distributed? - -Out of the new tokens that are generated: -- 65% go to the reward pool, which is split between authors and curators. -- 15% of the new tokens are awarded to holders of Hive Power. -- 10% of the new tokens are awarded to the Hive Proposal System. -- The remaining 10% pays for the witnesses to power the blockchain. - -^ -## Which exchanges are HIVE and HBD listed on? - -HIVE and HBD are listed on the following exchanges: - -| Exchange | HIVE | HBD | -| ------------- |:-------------:| -----:| -| [BitShares](https://wallet.bitshares.org/) | Y | Y | -| [BlockTrades](https://blocktrades.us) | Y | Y | -| [Ionomy](https://ionomy.com) | Y | N | - -^ -## What is the reward pool? - -Every day, a fixed amount of HIVE tokens are allocated to the network reward fund, commonly called the "reward pool." These get distributed to authors and curators for posting and voting on content. - -^ -## How is the reward pool split between authors and curators? - -Up to 50% of a post's payout is awarded to curators (the people who upvoted the post) as a reward for discovering the content. The other 50% is awarded to the author. - -If curators vote for a post within the first 5 minutes of it being created, a portion of their curation reward remains in the rewards pool for other authors/curators. This portion is linear to the age of the post between 0 and 5 minutes. As an example: upvoting at two minutes will donate 60% of your potential curation reward back to the rewards pool. - -^ -## Will the reward pool pay out more or less depending on who votes? - -There is a fixed amount of HIVE coins that gets added to the rewards pool each day. In the short term, the amount of coins that get paid out may be higher or lower depending on the amount of voting activity, but over time it will pay out the full amount of rewards regardless of who votes. - -Votes in Hive are stake-weighted. Therefore voters with more Hive Power have a greater influence over the allocation than voters with less HP, but their votes do not increase the amount of rewards in the rewards pool. - -^ -## Why do the earnings for my post go up or down? - -The amount that is shown next to a post is a "**Potential Payout**". This is an estimated value of how much money the post will make based on the votes that have occurred so far. Depending on various factors, this value can go up or down until the payout window closes: - -- If a post receives more upvotes, the potential payout of the post can go up. -- If a post receives more downvotes, the potential payout of the post can go down. -- If other posts receive more upvotes, the potential payout of the post can go down. -- If other posts receive more downvotes, the potential payout of the post can go up. -- If upvotes are removed from a post, the potential payout of the post can go down. -- If downvotes are removed from a post, the potential payout of the post can go up. -- If the price of HIVE goes up, the potential payout of all posts can go up. -- If the price of HIVE goes down, the potential payout of all posts can go down. - -^ -## When can I claim my rewards? - -Posts and comments remain active for 7 days. When the period is over, you are able to claim their earned rewards. In your Wallet, click the Claim Rewards button to add the tokens to your account. - -^ -## What is the difference between HIVE, Hive Power, and Hive Dollars? - -**HIVE** - HIVE is the base liquid currency token in the platform. HIVE can be powered up into Hive Power, traded for Hive Dollars, or transferred to other accounts. It is a cryptocurrency token, similar to bitcoin. - -**Hive Power** - Hive Power (abbreviated HP) is a measurement of how much influence a user has in the Hive network. The more Hive Power a user holds, the more they can influence the value of posts and comments. Hive Power is less liquid. If a user wishes to “Power Down” HP, they will receive equal distributions of the HIVE weekly, over a 13 week period. - -**Hive Dollars** - Hive Dollars (commonly abbreviated HBD) are liquid stable-value currency tokens designed to be pegged to $1 USD. Hive Dollars can be traded with HIVE, and transferred to other accounts for commerce or exchange. Hive Dollars may also be converted into HIVE via a process that takes 3.5 days. - -^ -## What is delegated Hive Power? - -Users have the option to delegate Hive Power to other users. When a user is delegated Hive Power - their Resource Credits, content votes, and curation rewards are calculated as if it were their own Hive Power. Users are not able to power down or cash out delegated Hive Power however, as it still belongs to the original owner. - -Most users will have a small amount of Hive Power delegated to them by the Hive account after creating an account. - -Delegated Hive Power shows up in a user's wallet below their actual Hive Power balance in parentheses. - -^ -## What determines the price of HIVE? - -The price of HIVE is based on the supply and demand of the token, as determined by buyers and sellers on the exchanges. It is similar to how the price of a commodity like gold is determined. - -^ -## How do I get more Hive Power? - -With HIVE tokens in your wallet, click "Power Up" to turn them into Hive Power. If you have Hive Dollars, you can convert them to HIVE from your wallet, and then power up the HIVE. - -If you don’t already have HIVE or Hive Dollars in your wallet, you can purchase them using bitcoin (BTC), Ether (ETH), Litecoin (LTC), or BitShares (BTS) tokens. You may purchase BTC on various exchanges, such as Coinbase.com or Localbitcoins.com. - -To buy: -- Click "Buy Hive" from the main menu in the top right corner of hive.blog, or from your wallet. -- Select the currency to deposit, and enter the amount of that currency you wish to use. -- Enter your Hive account name (without the @) for "Your receive address". -- Click the "Get Deposit Address" button. -- Send the currency to the provided address. - -HIVE purchases made via hive.blog are facilitated by BlockTrades. - -Bitcoin can also be exchanged for HIVE on external markets. - -^ -## How long does it take HIVE or Hive Power that I purchased to show up in my account? - -Transactions on the Hive blockchain typically only take about three seconds to process, but when you are purchasing the HIVE tokens using bitcoin or some other token, then the transaction must wait for the transaction to be confirmed on the other network. This can take several hours, and sometimes even days. - -If you paid using bitcoin, the third party website bitcoinfees.21.co can estimate the approximate wait time of the transaction based on the fees that were paid. The third party website blockchain.info will lookup the fees that were paid on a specific blockchain transaction. - -^ -## What is powering up and down? - -**Powering up** - If you have HIVE tokens, you can Power Up to Hive Power to get more voting influence on posts and comments. Having more Hive Power also increases the amount of curation rewards and new vested tokens that you can earn. More HP also grants more influence on approving Hive witnesses. - -**Powering down** - If you have Hive Power, you can power down to turn it into liquid HIVE over a period of time. The system will transfer 1/13 of your Hive Power to HIVE each week for about three months (13 weeks), starting 1 week from the time it is started. However, you will lose your influence in the network proportionally to how much is powered down, so think about it carefully. Power downs can be stopped at any time. - -^ -## What do the dollar amounts for pending payouts represent? - -The dollar amounts next to posts and comments are estimates of the potential payout that will occur when the payout period ends, based on the current voting activity and price of HIVE. These potential payout amounts may fluctuate up or down until the payout period ends. - -Payouts occur as a combination of Hive Power and Hive Dollars. Sometimes the blockchain may substitute HIVE in place of the Hive Dollars based on market conditions. - -The blockchain estimates the dollar value of HIVE and Hive Power based on the 3.5 day average price of HIVE reported by the witnesses. The blockchain assumes Hive Dollars are worth approximately one USD. - -^ -## Will 1 Hive Dollar always be worth $1.00 USD? - -The market value of a Hive Dollar is dictated by the supply and demand of the token. Therefore it is possible for 1 HBD to be worth more or less than 1 USD depending on market conditions. - -^ -## How do Hive Dollar to HIVE conversions work? - -If you convert Hive Dollars to HIVE, the blockchain will process the transaction over a period of 3.5 days. At the end of the 3.5 days, the HBD will be gone and replaced by approximately $1 USD worth of HIVE tokens. The "approximately 1 USD worth of HIVE tokens" is based on the median HIVE price over the 3.5 days, using the price feeds from the Hive witnesses. Depending on price fluctuations during the 3.5 days it is possible to end up with more or less than $1 USD worth of HIVE per HBD at the end of the conversion. This is an advanced user feature, and is currently only available to users using external wallets. - -^ -## Is there a way for me to convert my Hive Dollars to HIVE without waiting 3.5 days? - -You can exchange them. Visit the internal Market, found in the main menu. There you can exchange your HBD for HIVE in real-time at whatever the current market price is. - -^ -## What can I do with my HIVE tokens? +## Where do the new BUIDL token come from? -- "Power Up" to Hive Power -- Exchange for HBD in the internal market -- Withdraw to an exchange, and trade for BTC or other digital tokens -- Purchase items through third-party stores that accept HIVE tokens +Remember Build-it is built on top of [Hive Blockchain](), as such new tokens are +created every time a block is produced. Interestingly, Build-it allocates a majority of +the new tokens to a reward fund called “rewards pool” upon which users will be +rewarded for contributing value to the network. ^ -## What can I do with my HBD tokens? +## What is the Reward Pool? -- Convert to HIVE via your wallet (takes 3.5 days) -- Exchange for HIVE in the internal market -- Withdraw to an exchange, and trade for BTC or other digital tokens -- Purchase items through third-party stores that accept HBD tokens +Every 24hours a fixed amount of BUIDL tokens are allocated to the network reward +fund,commonly called the “rewards pool” as stated earlier. These tokens are then +fairly distributed to the authors and curators for adding value to the network. ^ -## What is a MVEST? +## How do I get more BUIDL tokens? -A VEST is a unit of measurement for Hive Power. A MVEST is one million VESTS. The amount of Hive Power in one MVEST can be found on hiveblocks.com as `hive_per_mvests`. +The BUIDL is a robust token that powers the Build-it network - it’s the currency that +operates on the network. They can be bought of the market, mined through upvotes +and cu-ration rewards pay for products and services, and many more. ^ -## Can I sell goods and services on Hive? +## So I earn digital tokens on my blog, how is that even possible? +Yes you can earn digital tokens on Build-it by the following methods below; -Other than making a post and making sales manually, there is no interface for selling items directly on hive.blog. You can list goods and services on the third-party websites and accept payment in Hive Dollars or HIVE. You also have the option to advertise your items through Hive posts. +- **Posting** - By publishing your contents/projects on the network, other +community members will share love by ‘upvoting’ your posts. The upvotes come in +BUIDL token,depending on the upvotes you receive, you may get a portion of the “rewards pool” -^ -## How can I withdraw my HIVE or HBD coins? - -HIVE and HBD tokens are readily tradable to bitcoin, which can be traded for the local currency of your choice. There is a link to "Sell" your HIVE and HBD tokens in your wallet, which uses the BlockTrades interface. - -^ -## Will I get a 1099 from Hive? - -No, you are not being paid by Hive. The Hive network rewards you. It is your responsibility to determine what, if any, taxes apply to the transactions you make. Further, it is your responsibility to report and remit the correct tax to the appropriate tax authority. Hive is not responsible for determining whether taxes apply to your Hive transactions, or for collecting, reporting, withholding, or remitting any taxes arising from any Hive transactions. - -^ -## How much are the transaction fees for sending tokens to other users? - -There are never any fees for transfers within the Hive network. However, if you transfer Hive to an exchange and convert it to another currency, you may incur a small fee from the exchange. - -^ -## Are there fees for Powering Up, Powering Down, trading on the internal market, or converting HBD to HIVE? - -No. None of these actions incur any fees. +- **Voting and curating** - Giving hearts to other authors for their valued content -^ -## How long does it take to transfer HIVE or HBD tokens between users? +is known as voting. If you discover a newly published post and vote on it before +others do,you earn a curation reward for that said content. Remember, the higher +your BUIDL POWER, the more curation reward you earn. -A transfer of tokens between accounts typically takes 3 seconds. This is far faster than most blockchain tokens. +- **Purchasing** - There are lots of ways you can become a holder of $BUIDL token, +for Hiveans, you can swap Hive for $buidl on +[Hive Engine](https://hive-engine.com/trade/BUIDL), +[Tribal Dex](https://tribaldex.com/trade/BUIDL), and +[Leo Dex](https://leodex.io/market/BUIDL). ^ -# Voting and Curating - -## What is my voting mana? - -Voting mana is like an "energy bar" in a computer game that goes down a little bit every time you vote. You start out with 100% voting mana. Every time you vote, you will use a small amount of your voting mana. - -As you use more of your voting mana, your votes will carry less influence. A vote with 50% voting mana left will be worth 1/2 as much as a vote cast with 100% voting mana. Not to worry, the network recharges your voting mana by 20% every day. - -^ -## How many times can I vote without depleting my voting mana? - -Every 100% vote you cast will use 2% of your remaining voting mana. Your voting mana will recharge by 20% each day. You can vote more than 10 times per day, but each vote will be worth less, and it will take longer to reach full voting mana again. - -Each user also has a mana pool specifically for down votes. This pool is 25% of the upvote mana pool. Downvotes will consume this mana first before consuming regular mana. When voting, 1/50th of the remaining mana is used for a 100% vote. The same math is used for downvote mana but multiplied by 4 because the downvote mana pool is 25% the size of the normal mana pool. The maximum of these values is used. When both mana pools are at 100%, the values are the same. When downvote mana runs out normal mana is used in the same manner as the current behavior. - -^ -## Can I vote with less than 100% of my voting strength? - -New users can only upvote and downvote with 100% voting strength. - -Once you reach about 500 Hive Power, you will see a vote slider appear when you vote. You can use the slider to adjust the weight of your vote, between 1% and 100% voting strength. Voting with less than 100% voting weight will use up less voting mana, but it will also have less of an influence on the post or comment's rewards. - - - -^ -## Where can I check my voting mana? - -You can view your current voting mana using third party tools such as https://hiveblocks.com/@youraccount. - -^ -## What determines how much of the curation reward goes to the author versus curators? - -The rewards are allocated so that 50% of the payout goes to the author of the post/comment, and 50% goes to the curator. - -Of the 50% that goes to the curator, the curator will receive less than 100% if the curator votes within the first 5 minutes. The split of the 50% that the curator receives during the first 5 minutes is calculated linearly based on the time the vote is cast. - -- If a post is upvoted the moment of posting, 100% of the curation reward will remain in the rewards pool. -- At 1 minute, the curator receives 20%, and 80% remains in the rewards pool. -- At 2 minutes, the curator receives 40%, and 60% remains in the rewards pool. -- At 3 minutes, the curator receives 60%, and 40% remains in the rewards pool. -- At 4 minutes, the curator receives 80%, and 20% remains in the rewards pool. -- If a post is upvoted 5 minutes (or later) after posting, 100% of the curation reward goes to the curator. - -^ -## Can I get curation rewards for upvoting comments? - -Yes. You can earn curation rewards from upvoting both posts and comments! - -^ -## Do I get curation rewards for downvoting posts or comments? - -No. Since downvoting reduces the rewards on a post/comment, it does not earn curation rewards. - -^ -## What are curation trails? - -Some users decide to use third party applications to automatically cast votes. Users can automatically vote for the same posts and comments that other users does. Typically they will set this up to follow the votes of users who are good at curating. When a user has other users automatically voting for the same content that they do, the people that automatically vote after them are called their "curation trail". - -^ -## Why don't my upvotes have an effect on a post's rewards? - -A user with more HP is going to have a larger influence on the rewards than users with less HP. One vote from a user with a lot of HP can often have more of an effect than 100 votes from users with a small amount of HP. - -Even though your vote may not have an immediate effect, when it gets added in along with all the other votes at the end of the payout period, it can still affect the payout. It may also cause more users to vote on the post too, because they saw that you upvoted it - so your votes can have an indirect effect on the payout this way. - -^ -## Is there a way to make my votes count for more? - -Yes. The more Hive Power you have, the more influence your votes will have. - -The platform does not require that anybody purchase HP in order to participate, and there are many users who have earned a lot of Hive Power without spending any of their own money. You have the option of purchasing more Hive Power through your Hive.blog wallet. - -^ -## What are the valid reasons for downvoting? - -Users are allowed to downvote for any reason that they want. There are many users in the community who recommend only using the downvote on posts that are abusive. It is up to you if you want to follow this etiquette. - -^ -## Does a downvote mean that I did something wrong? - -Just because you received a downvote does not mean that you did something wrong. The downvoting person may have just been voting to reallocate the rewards in a way that they felt was more beneficial to the other active posts in the platform. Often users will leave a comment explaining why they downvoted, but sometimes they might not. If they left a reason, it is up to you to determine if you did anything wrong, and if there is anything you want to change. - -^ -## Will a downvote hurt my reputation? - -Not necessarily. See: What causes my reputation score to go down? - -^ -# Plagiarism, Spam, and Abuse - -## What is considered spam or abuse? - -- Asking for money, views, upvotes, follows, or reblogs. -- Leaving nearly identical or materially similar comments on multiple posts. -- Comments that are unrelated to the topic of discussion. -- Sending unsolicited links or requests to users via wallet memos. -- Posts that require upvotes to enter or play in a contest or game. -- Sending users a link to your blog or a post if it is not relevant to the conversation. -- Posts or comments that include little or nothing more than an offer to trade follows or upvotes. -- Using tags that are unrelated to the post. -- Threatening users with any type of physical violence. -- Not citing sources when using someone else’s material. -- Posting ‘not safe for work’ content without using the “nsfw” tag. -- Selling or offering to buy votes/reblogs/follows, or schemes that facilitate this. -- Scams or Fraudulent offers. - -^ -## What are Hive.blog's policies on plagiarism? - -If you are posting plagiarized or copied content, you can get in legal trouble for violating copyright laws. Plagiarized posts and spam are seen as abuse and will be downvoted by community members. If you are posting or using someone else’s content, you must ensure that you have the rights to use the content, and properly reference the sources where you got the material from. - -^ -## Is it okay to use random pictures from the internet? - -If you are using an image that is not your own, make sure you are allowed to use the image, and cite the source of the image. - -Using random pictures from the internet without giving credit is discouraged. You may, however, use photos from “free image” websites such as Pexels.com or Pixabay.com. All photos on Pexels and Pixabay are free for personal and commercial use. - -^ -## Where do I report a post or comment that contains plagiarism, spam, or abuse? - -You can report any abusive content to the #hiveabuse channel on OpenHive.Chat. - -^ -# Reputation - -## What is Reputation? - -Every user has a reputation score next to their name. The reputation score is one way Hive measures the amount of value you have brought to the community. It is also a mechanism that is designed to help reduce abuse of the Hive platform. - -Your reputation goes up when accounts vote on your content. Getting downvoted by someone with a higher reputation can push your reputation down and make your posts less visible. - -Users with a lower reputation score are unable to affect your reputation. - -^ -## How is the Reputation score measured? - -Every new user starts off with a reputation score of 25. - -The reputation score is based off of a `log10` system, which means that a score of 40 is about 10x better than a score of 30. - -More information about the calculation of the reputation score can be found in this post from @digitalnotvir: -https://hive.blog/steemit/@digitalnotvir/how-reputation-scores-are-calculated-the-details-explained-with-simple-math - -^ -## How do I improve my reputation score? - -Every time another user upvotes one of your posts or comments, it increases your reputation score. The more Hive Power that the voter has, the larger the effect is. The best way to earn upvotes is by adding value to the Hive community. - -^ -## What causes my reputation score to go down? - -The only way for your reputation score to go down is to be downvoted by another user. Not all downvotes will cause a reputation loss though. - -- Downvotes from users with a lower reputation score than you will not hurt your score. -- If your post or comment that was downvoted still received more upvotes than downvotes (weighted by HP), then the net effect on your reputation score will still be positive. - -^ -## Why does my reputation score matter? - -A reputation score is one way Hive measures the amount of value you have brought to the community. In real estate, they say there are three variables of the utmost importance: location, location, location. On Hive, those things are: reputation, reputation, reputation. It’s not to say other variables aren’t important, but reputation will be an enormous factor in your level of success. - -Many users glance at other users’ reputation scores when deciding which articles to read because they know higher reputation scores means it is much more likely quality content. Furthermore, the higher your rep, the more effect your vote will have on the reputation of others. - -It is worth noting that if your reputation score goes below 0, Hive.blog will hide your posts and comments making it very difficult to gain monetary rewards and followers. This incentivizes online etiquette and respect for your fellow users. - -^ -# Followers, Feeds, and Rehiving - -## What is Rehiving? - -This is like reblogging or sharing posts on other platforms. Once you rehive a post it will appear in your feed and in your followers' feeds as if you had posted it yourself. Use it conservatively and with caution. It is great to want to share content you like and appreciate with people you follow, but you don't want to overwhelm your followers either. - -^ -## Can I share on other social media? - -Yes you can use the share button to share on Facebook, Twitter or LinkedIn. You are welcome to post your Hive links on other websites and social media sites. - -^ -# Blockchain - -## What is a blockchain? - -A blockchain is a public ledger of all transactions ever executed. All of the transactions and data are stored in a distributed database. Each time the database is updated, all of updates are done together in a batch called a 'block'. Each time a new block is produced/added, it is appended on to all of the previous blocks - hence the name "blockchain". - -^ -## What is the Hive blockchain? - -The Hive blockchain is the publicly accessible distributed database, which records all posts and votes, and distributes the rewards across the network. It is where all of the text content and voting data is stored, and it is where all of the reward calculations and payouts are performed. - -^ -## How do Resource Credits work on the Hive blockchain? - -Since transacting on the Hive blockchain has zero fees, transaction rate-limiting is employed to safeguard the blockchain from spam attacks. Everything action that you take on the blockchain will consume a small amount of Resource Credits. This includes posting, commenting, voting, transferring tokens, etc. Viewing content does not consume Resource Credits. - -Every user has a limited amount of Resource Credits to use each week. The more transactions a user does, the less Resource Credits they will have left (until they recharge). Users with more Hive Power will have more Resource Credits. - -When the blockchain becomes busy (due to heavy use), the Resource Credit cost of transactions may become higher than during times when the blockchain is less busy. - -You can check how many Resource Credits you currently have at: -https://hiveblocks.com/@youraccount - -If users do not have enough Resource Credits, they will be unable to transact with the blockchain until their Resource Credits recharge or they acquire additional Hive Power to increase their Resource Credit balance. - -If you get an error that you have exceeded your Resource Credit allowance, it is normally best to just wait and try again later. Usually if you wait and try again later, the transaction will go through. - -If you are unable to transact for extended periods of time, or you are frequently running into Resource Credit limits, then you will either need to reduce your usage to stay within your limit, or purchase more Hive Power for your account. - -^ -## What is the difference between Hive and Hive.blog? - -Hive is the name of the blockchain that stores all of the data and transactions, and processes all of the events that take place. HIVE is also a name for the system’s value token (currency). - -Hive.blog is a front end web interface to interact with the blockchain, and view the blockchain data. - -^ -## How is Hive different from Bitcoin? - -On a technical level, the two networks rely on the same model of a blockchain, but are built upon different technologies and codebase. Hive is based on a new state-of-the-art blockchain technology called Graphene, which uses "witnesses" instead of "miners" to produce blocks. - -The "delegated proof of stake" model of using witnesses instead of miners allows for greater efficiency in block production. With BTC, 100% of the new coins that are created are allocated to block producers (miners). With the Hive blockchain, only 10% of the new coins are paid to block producers (witnesses). The other 90% of new HIVE coins are awarded to content producers, curators, and Hive Power holders. - -^ -## What is the difference between Proof of Work, Proof of Stake, and Delegated Proof of Stake? - -**Proof of work** - Miners solve a complex mathematical problem. The miner that solves the problem first adds the block to the blockchain. The network rewards the miner for doing so. - -**Proof of stake** - Requires ownership, or stake, in the cryptocurrency. The more tokens you own, the more block creation power you have. Benefits: eliminates the need for expensive mining rigs, runs on a tiny fraction of the power, and it requires block producers to have a stake in the network. - -**Delegated proof of stake** - Block-creating accounts, called witnesses, are collectively approved by Hive stakeholders. Instead of relying on proof of work to find blocks, the Hive network actively schedules these accounts to improve the time between blocks to 3 seconds. - -^ -## How often does the Hive blockchain produce a new block? - -The Hive blockchain schedules witnesses to produce a new block every 3 seconds. 21 witness nodes produce 21 blocks in each 63-second round. - -^ -## Is there a way to see the raw data that is stored in the blockchain? - -Yes. The blockchain data can be viewed in different ways with third-party tools such as hiveblocks.com. - -^ -## Where can I find the information for the official launch of the blockchain? - -See this post by @hiveio - Announcing the Launch of Hive Blockchain - -^ -## Can I mine HIVE? - -No. Proof of work mining has been removed from Hive. - -^ -# Security - -## How can I keep my Hive account secure? - -Save your master password and keep it somewhere safe. - -Only log into your account using the key with the appropriate permissions for what you are doing: -- Posting key for every day logins -- Active key when necessary for transfers, power ups, etc. -- Master password or owner key when changing the password - -Again, save your master password and keep it safe! If logging in with your post key, make sure you don't overwrite or misplace your original master password. - -It is not recommended to share your password or keys with any third party site. - -^ -## Why should I be careful with my master password? - -The master password is used to derive all keys for your account, including the owner key. If someone has access to your master password, they can steal your account and all of the tokens in it. - -^ -## Why is the master password a long string of gibberish? - -The password has to be long and random for maximum account security. - -^ -## What are my different keys for? - -**Posting key** - The posting key allows accounts to post, comment, edit, vote, reblog, and follow or mute other accounts. Most users should be logging into Hive every day with the posting key. You are more likely to have your password or key compromised the more you use it so a limited posting key exists to restrict the damage that a compromised account key would cause. - -**Active key** - The active key is meant for more sensitive tasks such as transferring funds, power up/down transactions, converting Hive Dollars, voting for witnesses, updating profile details and avatar, and placing a market order. - -**Memo key** - Currently the memo key is not used. - -**Owner key** - The owner key is only meant for use when necessary. It is the most powerful key because it can change any key of an account, including the owner key. Ideally it is meant to be stored offline, and only used to recover a compromised account. - -^ -## What do I do if I lost my password/keys? - -There is no way to recover your account if you lose your password or owner key! Because your account has real value, it is **very important** that you save your master password somewhere safe where you will not lose it. - -It is strongly recommended that you store an offline copy of your password somewhere safe in case of a hard drive failure or other calamity. Consider digital offline storage, such as an external disk or flash drive, as well as printed paper. Use a safe deposit box for best redundancy. - -^ -## Are my HIVE and Hive Dollar tokens insured in the event of a hack or if someone takes over my account? - -No, liquid tokens can not be taken back if stolen or sent to the wrong account. - -If your tokens are in Hive Power, it is impossible for a hacker to take out more than 1/13 per week. If your tokens are in savings, there is a three-day wait period for them to become transferable. - -^ -## How do I report a security vulnerability? - -If you find a security issue please report the details to privacy@hive.io. - -^ -# Developers - -## Are the Hive blockchain and hive.blog code open-source? - -Yes. Both the Hive blockchain and hive.blog are open-source projects. - -Developers should however avoid the use of the term "Hive.blog" in their own products, and instead refer to the Hive Blockchain or Hive Platform. - -^ -## Is there a Github page for hive.blog? - -Yes. Will be made public after launch - -^ -## Is there a Github page for the Hive blockchain? - -Yes. Will be made public after launch - -^ -## What is available for developers interested in Hive and Hive.blog? - -Many software engineers are currently leveraging the open-source code to build their applications on Hive - -The [Hive Developer Portal](https://developers.hive.io/) also contains documents and resources for developing tools and applications for the Hive blockchain. - -^ -## How do I use cli_wallet? - -Here is a guide from the user @pfunk explaining how to use the cli_wallet: -https://hive.blog/steemhelp/@pfunk/a-learner-s-guide-to-using-steem-s-cliwallet-part-1 - -^ -# Witnesses - -## What are Hive witnesses? - -The Hive blockchain requires a set of people to create blocks and uses a consensus mechanism called delegated proof of stake, or DPOS. The community elects 'witnesses' to act as the network's block producers and governance body. There are 20 full-time witnesses, producing a block every 63-second round. A 21st position is shared by the backup witnesses, who are scheduled proportionally to the amount of stake-weighted community approval they have. Witnesses are compensated with Hive Power for each block they create. - -^ -## How can I vote for witnesses? - -Visit https://wallet.hive.blog/~witnesses. - -^ -## How many witnesses can I vote for? - -Each account can vote for up to 30 witnesses. - -^ -# Miscellaneous - -## Where can I ask for help if my question was not answered here? - -If you post your question in the #help channel on OpenHive.Chat, the users there may be able to help. -Alternatively, there is a Hive Discord channel that may be able to answer your questions. - -You can also create a post on hive.blog with the tag #help, and someone in the community may be able to answer it. - -^ - -# Disclaimer - -## Third Party References and User Links - -Any services listed here, as well as any other third party applications or websites that are listed, does not constitute and endorsement or recommendation on behalf of the operators of Hive.blog +## What do I do with the tokens? +Below are some pretty basic usecases for the $buidl token: -Please use the third party tools and content at your own risk. +- Low-cost transactions - one of the most popular usecase for most tokens is for +sending and receiving payments for products and services at an increasingly low fee +and 3sec transaction speed. +- Proof of censorship-resistant store of value - While our local bank keeps freezing -^ +and blocking our bank assets accounts, truth be told - this act occurs more often +than people realize most. Holding the $buidl token gives each holder a +censorship-resistant alternative store of wealth as long as they have their private +keys saved in a third-party trusted platform. +- Being the first early investors of the start-up - Without a doubt, the emergence of +digital assets is giving everyone with an internet connection to become an investor in +an early-stage. While we build more usecases for the $buidl token, the demand will +increas thus increasing the store of value for every holder. diff --git a/src/app/help/en/welcome.md b/src/app/help/en/welcome.md index 3de84453d..22e350630 100644 --- a/src/app/help/en/welcome.md +++ b/src/app/help/en/welcome.md @@ -1,71 +1,11 @@ -## Welcome to Hive! +## Welcome and Thank you! *** -Now that you have an account, here's how to get started. - -*** - -### 1. Backup your password - -Unlike centralized web services, **it is not possible to recover lost passwords on the Hive blockchain**. - -You are entirely responsible for saving your password, backing it up, and keeping it secure. -Never put your password into unverified third party websites as they may steal your account. - - -### 2. Really, backup your password! - -Store an **offline copy** of your password somewhere safe in case of a hard drive failure or other calamity. -Consider using a flash drive, secure cloud storage, or simply print it on paper. - - -### 3. Some ground rules - -1. It is free to post, comment, and vote on all content at hive.blog. -2. Do not plagiarize, and be sure to cite sources, including copyrighted images. - - -### 3. Update your profile - -You can update your profile on the Settings page. -This includes your display name, location, about information, and website. - - -### 4. Create your "Introduceyourself" Post - -The tradition for new users is to create an "introduceyourself" post in order -to let the community get to know you. You can verify other social media -accounts (Twitter, Facebook, etc.) by sharing the link to your Hive account -from those profiles. - - -### 5. Sign up for Hive Chat - -A lot of users mingle and chat in [OpenHive.Chat](https://openhive.chat/). It is a -great place to meet people! - -You log in with your Hive account, using Private Posting Key to confirm your identity. -Ask questions in the [\#help](https://openhive.chat/channel/help) channel. - - -### 6. Voting and Tokens - -Posts and comments can accrue votes over a period of 7 days. Projected payouts -will fluctuate (up and down) and no payout is guaranteed. If a post receives -enough votes for a payout, it will be split between the author (at least 50%) -and voters ("curators"). - -HIVE, Hive Power (HP) and Hive Dollars (HBD) are the three forms of digital -currency used by the Hive Blockchain. More information -[here](https://hive.blog/faq.html#What_is_the_difference_between_HIVE__HIVE_Power__and_Hive_Dollars). +Welcome to our newsletter. +The Build-it newsletter is the best way to get diy tips, contests, general updates and many more delivered to your mail! -##### Additional resources +[click here to go back](/). -- [FAQ](https://hive.blog/faq.html) - Answers to commonly asked questions -- [Hive Bluepaper](https://hive.io/hive-bluepaper.pdf) - Explanation of how the platform works -- [Hive Whitepaper](https://hive.io/hive-whitepaper.pdf) - Technical details of the Hive blockchain -- [Apps Built on Hive](https://hiverojects.com/) - Directory of apps, sites and tools built by Hive community -- [Hive Block Explorer](https://hiveblocks.com/) - Shows the raw Hive blockchain data diff --git a/src/app/locales/add_missing.js b/src/app/locales/add_missing.js index 2a94de58d..3d12228e4 100644 --- a/src/app/locales/add_missing.js +++ b/src/app/locales/add_missing.js @@ -1,4 +1,4 @@ -// for LANG in es fr it ja ko pl ru zh; do echo $LANG; node add_missing.js en $LANG > tmp.json ; mv tmp.json $LANG.json ; done +// for LANG in es fr it ja ko pl ru zh ua; do echo $LANG; node add_missing.js en $LANG > tmp.json ; mv tmp.json $LANG.json ; done const fs = require('fs'); const lodash = require('lodash'); diff --git a/src/app/locales/counterpart/ua.js b/src/app/locales/counterpart/ua.js new file mode 100644 index 000000000..f5640c7d2 --- /dev/null +++ b/src/app/locales/counterpart/ua.js @@ -0,0 +1,30 @@ +// The translations in this file are added by default. + +'use strict'; + +module.exports = { + counterpart: { + names: require('date-names/en'), + pluralize: require('pluralizers/en'), + + formats: { + date: { + default: '%a, %e %b %Y', + long: '%A, %B %o, %Y', + short: '%b %e', + }, + + time: { + default: '%H:%M', + long: '%H:%M:%S %z', + short: '%H:%M', + }, + + datetime: { + default: '%a, %e %b %Y %H:%M', + long: '%A, %B %o, %Y %H:%M:%S %z', + short: '%e %b %H:%M', + }, + }, + }, +}; diff --git a/src/app/locales/en.json b/src/app/locales/en.json index a97c53d6c..c653d5379 100644 --- a/src/app/locales/en.json +++ b/src/app/locales/en.json @@ -348,7 +348,7 @@ "selecting_them": "selecting them", "image_upload": "Image upload", "power_up_100": "Power Up 100%%", - "default_50_50": "50%% HBD / 50%% HP", + "default_50_50": "50%% liquid / 50%% staked", "decline_payout": "Decline Payout", "check_this_to_auto_upvote_your_post": "Check this to auto-upvote your post", diff --git a/src/app/locales/ua.json b/src/app/locales/ua.json new file mode 100644 index 000000000..d22493a3d --- /dev/null +++ b/src/app/locales/ua.json @@ -0,0 +1,1019 @@ +{ + "g": { + "administrator": "admin", + "administrators": "admins", + "age": "вік", + "amount": "Кількість", + "and": "і", + "are_you_sure": "Ви впевнені?", + "ask": "Пропозиції (ASK)", + "balance": "Баланс: %(balanceValue)s-и", + "balances": "Баланси", + "beneficiaries": "Beneficiaries", + "bid": "Купівля", + "blog": "Мій Блог", + "profile": "Мій профіль", + "browse": "Browse", + "buy": "Купити", + "buy_hive_power": "Buy Hive Power", + "buy_or_sell": "Купити або Продати", + "by": "опублікував(-ла)", + "cancel": "Cкасувати", + "change_password": "Змінити пароль", + "choose_language": "Виберіть мову", + "clear": "Очистити", + "close": "Закрити", + "collapse_or_expand": "Згорнути/Розгорнути", + "communities": "Communities", + "community_settings_header": "Community Settings", + "community_settings_description": "What does your community stand for?", + "community_list_header": "Communities", + "community_user_role_add_description": + "Set the role of a user in this community.", + "community_user_role_edit_description": + "Changing @%(username)s's role.", + "community_user_role_add_header": "Add User Role", + "community_user_role_edit_header": "Change User Role", + "community_user_title_edit_header": "Change User Title", + "community_user_title_edit_description": "Set Title for @%(username)s", + "comments": "Коментарі", + "confirm": "Підтвердіть", + "convert": "Конвертувати", + "date": "Дата", + "delete": "Видалити", + "dismiss": "Відхилити", + "edit": "Редагувати", + "email": "Електронна пошта", + "external_link_message": "Це джерело знаходиться за межами hive.blog", + "affiliation_steemit": "Команда Hive.", + "affiliation_sm": "Команда Steem Monsters", + "affiliation_hive": "Hive Team", + "feed": "Стрічка", + "flag": "Flag", + "flag_this_post": "Flag this %(type)s", + "flag_this_post_description": + "Please provide a note regarding your decision to flag this %(type)s, it will be reviewed by community moderators.", + "follow": "Слідкувати", + "for": " за ", + "from": " від ", + "go_back": "Назад", + "hide": "Приховати", + "in": "в", + "in_reply_to": "у відповідь на", + "insufficient_balance": "Недостатній баланс", + "invalid_amount": "Недійсна сума", + "links": "Мої посилання", + "joined": "Приєднався(-лася)", + "loading": "Завантаження", + "login": "Вхід", + "logout": "Вийти", + "memo": "Пам'ятка", + "mute": "В бан", + "my_blog": "Мій блог", + "my_comments": "Мої коментарі", + "my_feed": "Моя стрічка", + "my_replies": "Відповіді для вас", + "my_wallet": "Мій гаманець", + "new": "Нове", + "newer": "Найновіше", + "next": "Наступний", + "no": "Ні", + "notifications": "Notifications", + "ok": "Добре", + "older": "Найстаріший", + "or": "або", + "order_placed": "Замовлення розміщено: %(order)s-і", + "password": "Пароль", + "payouts": "Виплати", + "permissions": "Дозволи", + "phishy_message": + "Джерело розширено до простого тексту; остерігайтеся потенційної спроби фішингу!", + "pin": "Pin", + "post": "Допис", + "post_as_user": "Опублікувати як %(username)s", + "posts": "Усі Дописи", + "powered_up_100": "100%% у Силі Голосу", + "preview": "Попередній перегляд", + "previous": "Попередній", + "price": "Ціна", + "print": "Видрукувати", + "promote": "Просувати", + "promoted": "Промо-контент", + "re": "RE", + "re_to": "RE: %(topic)s", + "read_offical_blog": "Hive Блог", + "recent_password": "Останній пароль", + "receive": "Отримувати ", + "remove": "Видалити", + "remove_vote": "Прибрати голос", + "replied_to": "відповів(ла) на %(account)s", + "replies": "Відповіді", + "reply": "Відповісти", + "reply_count": { + "zero": "жодних відповідей", + "one": "1 відповідь", + "other": "%(count)s відповіді(-ей)" + }, + "reputation": "Репутація", + "reveal_comment": "Розкрийте коментар", + "will_be_hidden_due_to_low_rating": + "Буде приховано через низький рейтинг", + "request": "запит", + "required": "Вимагається", + "rewards": "Нагороди", + "save": "Зберегти", + "saved": "Збережено", + "search": "Пошук", + "sell": "Продати", + "settings": "Налаштування", + "share_this_post": "Поділитися цим дописом", + "mute_this_post": "Mute post/comment", + "mute_this_post_description": + "Please provide a note regarding your decision to mute this content.", + "unmute_this_post": "Unmute this post/comment", + "unmute_this_post_description": + "Please provide a note regarding your decision to unmute this content.", + "show": "Показати", + "sign_in": "Увійти", + "sign_up": "Зареєструватися", + "since": "з тих пір %(date)s", + "submit": "Надіслати", + "subscriptions": "Subscriptions", + "power_up": "Голосувати за", + "submit_a_story": "Опублікувати", + "tag": "Тег", + "to": " до ", + "topics": "Теми", + "toggle_nightmode": "Увімкнути нічний режим", + "all_tags": "Усі теги", + "all_tags_mobile": "Весь вміст", + "transfer": "Передача ", + "trending_topics": "Модні теми", + "try_again": "Спробуйте ще раз", + "type": "Тип", + "unfollow": "Не слідкувати", + "unmute": "Увімкнути читання", + "unpin": "Unpin", + "unknown": "Невідомий", + "upvote": "Проголосувати", + "upvote_post": "Проголосувати за допис", + "username": "Ім'я користувача", + "version": "Версія", + "vote": "Голосувати", + "votes": "голосів", + "wallet": "Гаманець", + "warning": "warning", + "yes": "Так", + "posting": "Публікація", + "owner": "Власник", + "active": "Активний", + "account_not_found": "Обліковий запис не знайдено", + "this_is_wrong_password": "Це неправильний пароль", + "do_you_need_to": "Вам потрібно", + "account_name": "Назва рахунку", + "recover_your_account": "відновити свій рахунок", + "reset_usernames_password": "Скидання %(username)s Паролю", + "this_will_update_usernames_authtype_key": + "Це оновить %(username)s %(authType)s ключ", + "passwords_do_not_match": "Паролі не співпадають", + "you_need_private_password_or_key_not_a_public_key": + "Вам потрібен приватний пароль або ключ (не відкритий ключ)", + "the_rules_of_APP_NAME": { + "one": "Перше правило %(APP_NAME)s - не втрачайте свій пароль.", + "second": "Друге правило %(APP_NAME)s - Не втрачайте свій пароль.", + "third": + "Третє правило %(APP_NAME)s - Ми не можемо відновити ваш пароль.", + "fourth": + "Четверте правило: якщо ви можете запам'ятати пароль, він не захищений.", + "fifth": + "П'яте правило: використовуйте лише випадково згенеровані паролі.", + "sixth": "Шосте правило: нікому не вказуйте свій пароль.", + "seventh": + "Сьоме правило: Завжди створюйте резервну копію свого пароля." + }, + "recover_password": "Відновити обліковий запис", + "current_password": "Поточний пароль", + "generated_password": "Створений пароль", + "backup_password_by_storing_it": + "Створіть резервну копію, зберігаючи її в диспетчері паролів або текстовому файлі", + "enter_account_show_password": + "Введіть дійсне ім’я облікового запису, щоб показати пароль", + "click_to_generate_password": "Натисніть, щоб створити пароль", + "re_enter_generate_password": "Повторно введіть створений пароль", + "understand_that_APP_NAME_cannot_recover_password": + "Я розумію, що %(APP_NAME)s не зможу відновити втрачені паролі", + "i_saved_password": "Я надійно зберег свій створений пароль", + "update_password": "Оновити пароль", + "confirm_password": "Підтвердьте пароль", + "account_updated": "Обліковий запис оновлено", + "password_must_be_characters_or_more": + "Пароль повинен бути %(amount)s-в символів або більше", + "need_password_or_key": + "Вам потрібен приватний пароль або ключ (не відкритий ключ)", + "login_to_see_memo": "увійдіть, щоб побачити нагадування", + "new_password": "Новий пароль", + "incorrect_password": "Неправильний пароль", + "username_does_not_exist": "Ім'я користувача не існує", + "account_name_should_start_with_a_letter": + "Назва обліковки має починатися з літери.", + "account_name_should_be_shorter": + "Назва облікового запису повинна бути коротшою.", + "account_name_should_be_longer": + "Назва облікового запису повинна бути довшою.", + "account_name_should_have_only_letters_digits_or_dashes": + "У назві обліковки повинні бути лише літери, цифри, крапки або тире.", + "cannot_increase_reward_of_post_within_the_last_minute_before_payout": + "Неможливо збільшити винагороду за повідомлення за останню хвилину перед виплатою", + "only_one_APP_NAME_account_allowed_per_ip_address_every_10_minutes": + "Лише один акаунт Steem дозволений на IP-адресу кожні 10 хвилин", + "reblog_this_post": "Reblog This Post", + "reblog": "Рехайв", + "write_your_story": "Напишіть свою історію ...", + "remember_voting_and_posting_key": + "Запам’ятайте ключі для голосування та створення записів", + "auto_login_question_mark": "Автоматичний вхід?", + "hide_private_key": "Сховати приватний ключ", + "show_private_key": "Показати приватний ключ", + "login_to_show": "Увійдіть, щоб показати", + "not_valid_email": "Неправильна електронна пошта", + "thank_you_for_being_an_early_visitor_to_APP_NAME": + "Дякуємо, що були першим відвідувачем %(APP_NAME)s. Ми повернемось до вас при першій можливій нагоді.", + "author_rewards": "Авторські нагороди", + "curation_rewards": "Кураторські нагороди", + "sorry_your_reddit_account_doesnt_have_enough_karma": + "На жаль, у вашому обліковому записі Reddit недостатньо Reddit Karma, щоб отримати право на безкоштовну реєстрацію. Будь ласка, додайте свій електронний лист для місця у списку очікування", + "register_with_facebook": "Зареєструватися за допомогою Facebook", + "or_click_the_button_below_to_register_with_facebook": + "Або натисніть кнопку нижче, щоб зареєструватися за допомогою Facebook", + "server_returned_error": "Упс.. Ми отримали помилку.", + "APP_NAME_support": "%(APP_NAME)s Підтримка", + "please_email_questions_to": + "Свої запитання надішліть електронною поштою", + "next_7_strings_single_block": { + "authors_get_paid_when_people_like_you_upvote_their_post": + "Автори отримують зарплату, коли такі люди, як ви, вподобають їх допис", + "if_you_enjoyed_what_you_read_earn_amount": + "Якщо вам сподобалося те, що ви прочитали тут, створіть свій акаунт сьогодні і почніть заробляти БЕЗКОШТОВНІ STEEM!", + "free_hive": "FREE HIVE!", + "sign_up_earn_hive": "Sign up now to earn ", + "free_steem": "БЕЗКОШТОВНІ STEEM!", + "sign_up_earn_steem": "Зареєструйтесь зараз, щоб заробити " + }, + "next_3_strings_together": { + "show_more": "Показати більше", + "show_less": "Показати менше", + "value_posts": "допис низької вартості" + }, + "read_only_mode": + "Завдяки сервісному обслуговуванню ми працюємо лише в режимі читання. Просимо вибачення за незручності.", + "tags_and_topics": "Теги", + "show_more_topics": "Переглянути всі теги", + "basic": "Основні", + "advanced": "Розширений", + "views": { + "zero": "Ніхто не бачив", + "one": "%(count)s Переглянуло", + "other": "%(count)s Переглянуло" + }, + "responses": { + "zero": "Немає відповідей", + "one": "%(count)s Відповідь", + "other": "%(count)s Відповідей" + }, + "post_key_warning": { + "confirm": + "Ви збираєтесь опублікувати приватний ключ STEEM або головний пароль. Ви, ймовірно, втратите контроль над пов’язаним обліковим записом та всіма його коштами.", + "warning": + "Законні користувачі, включаючи співробітників Hive Inc., ніколи не просять у вас приватного ключа або головного пароля.", + "checkbox": "Я розумію" + }, + "total": "Усього емітовано", + "circulating": "В обігу", + "burn": "Спалено", + "staking": "Застекано", + "written_from": "Posted from %(app_name)s", + "all_claim_started": + "Claiming all pending tokens. It will be completed after about %(seconds)s seconds.", + "all_claim_completed": "Claims completed!", + "buy_steem_power": "Купити Steem Силу", + "resteem_this_post": "Рехайв цього Допису" + }, + "navigation": { + "about": "Про Hive, Inc.", + "explore": "Досліджувати", + "APP_NAME_whitepaper": "%(APP_NAME)s Whitepaper", + "third_party_exchanges": "Сторонні біржі:", + "buy_LIQUID_TOKEN": "Купити %(LIQUID_TOKEN)s-и", + "sell_LIQUID_TOKEN": "Продати %(LIQUID_TOKEN)s-и", + "currency_market": "Ринок валюти", + "stolen_account_recovery": "Відновлення викрадених рахунків", + "change_account_password": "Змінити пароль облікового запису", + "vote_for_witnesses": "Голосувати за Блок Продюсерів", + "advertise": "Реклама", + "media_kit": "Media Kit", + "privacy_policy": "Політика конфіденційності", + "terms_of_service": "Умови обслуговування", + "sign_up": "Зареєструватися", + "learn_more": "Дізнатись більше", + "welcome": "Ласкаво просимо", + "faq": "FAQ", + "chat": "Steem Чат", + "app_center": "Програми, побудовані на Hive", + "business_center": "Компанії, які приймають Steem", + "api_docs": "Документи Hive API", + "bluepaper": "Steem Bluepaper", + "smt_whitepaper": "SMT Whitepaper", + "whitepaper": "Steem Whitepaper", + "intro_tagline": "Ваш голос чогось вартий", + "intro_paragraph": + "Отримуйте платню за хороший вміст. Опублікуйте та опублікуйте статті на Hive, щоб отримати свою частку в щоденному пулі нагород.", + "jobs": "Робота в Hive", + "hive_proposals": "Hive Proposals", + "blockexplorer": "Blockexplorer", + "what_is_hive": "What is Hive?", + "block_explorer": "Block Explorer", + "steem_proposals": "Hive Пропозиції" + }, + "main_menu": { + "hot": "Гаряче", + "trending": "Популярне" + }, + "reply_editor": { + "shorten_title": "Скоротіть назву", + "exceeds_maximum_length": "Максимальна довжина перевищує (%(maxKb)sKB)", + "including_the_category": " (включаючи категорію '%(rootCategory)s')", + "use_limited_amount_of_tags": + "Ви маєте %(tagsLength)s-ів, всього тегів%(includingCategory)s. Будь ласка, використовуйте лише 5 у своєму дописі та рядку категорії.", + "are_you_sure_you_want_to_clear_this_form": + "Ви впевнені, що хочете вийти з редактора коментарів?", + "uploading": "Завантаження...", + "draft_saved": "Чернетка збережена.", + "editor": "Редактор", + "enable_markdown_editor": "Enable Markdown Editor", + "view_html_source": "View HTML source", + "insert_images_by_dragging_dropping": + "Вставте світлину, перетягуючи та опускаючи, ", + "pasting_from_the_clipboard": "обклеювання з буфера обміну, ", + "selecting_them": "виберіть їх", + "image_upload": "Завантажити світлину", + "power_up_100": "Сила Голосу 100%%", + "default_50_50": "50%% HBD / 50%% HP", + "decline_payout": "Відхилити виплату", + "check_this_to_auto_upvote_your_post": + "Перевірте це, щоб автоматично оновити свою публікацію", + "markdown_styling_guide": "Посібник зі стилізації по розмітки", + "or_by": "або за", + "title": "Назва допису", + "update_post": "Оновити допис", + "markdown_not_supported": "Тут не підтримується розмітка*Markdown", + "advanced_settings": "Розширені налаштування", + "beneficiaries_set": "%(count)s набір", + "tags_input": "Enter your tags separated by a space", + "enable_sidebyside": "Enable side-by-side editor", + "disable_sidebyside": "Disable side-by-side editor", + "post_options": "Post options" + }, + "beneficiary_selector_jsx": { + "header": "Хто повинен отримати нагороди?", + "add": "Додати обліковий запис", + "exceeds_max_beneficiaries": "Може мати не більше 8 бенефіціарів", + "beneficiary_cannot_be_self": "Не можна вказати себе як бенефіціара", + "beneficiary_cannot_be_duplicate": "Бенефіціар не може бути дублікатом", + "beneficiary_percent_invalid": + "Процент бенефіціара повинен становити від 1-100", + "beneficiary_percent_total_invalid": + "Загальний відсоток бенефіціара повинен бути менше 100" + }, + "post_template_selector_jsx": { + "templates": "Post templates", + "templates_description": + "Manage your post templates, other settings here will also be saved/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": + "Тег (до 8 тегів), перший тег - ваша основна категорія.", + "select_a_tag": "Виберіть тег", + "maximum_tag_length_is_24_characters": + "Максимальна довжина тегу - 24 символи", + "use_limited_amount_of_categories": + "Будь ласка, використовуйте лише %(amount)s категорії", + "use_only_lowercase_letters": "Використовуйте лише малі літери", + "use_one_dash": "Використовуйте лише один тире", + "use_spaces_to_separate_tags": + "Використовуйте пробіли для розділення тегів", + "use_only_allowed_characters": + "Використовуйте лише малі літери, цифри та один тире", + "must_start_with_a_letter": "Починати треба з літери", + "must_end_with_a_letter_or_number": + "Потрібно закінчити буквою або цифрою", + "must_not_include_hivemind_community_owner": + "Post already has %(hive)s tag by default, please do not include another 'hive-' tag." + }, + "post_advanced_settings_jsx": { + "payout_option_header": "Авторські нагороди", + "payout_option_description": + "Який тип жетонів ви хочете отримати як винагороду від цієї публікації?", + "current_default": "За замовчуванням", + "update_default_in_settings": "Оновлення", + "load_template": "Load template", + "delete_template": "Delete template", + "max_accepted_payout": "Maximum Accepted Payout", + "max_accepted_payout_description": + "HBD value of the maximum payout this post will receive.", + "unlimited": "No limit", + "custom_value": "Custom value" + }, + "postfull_jsx": { + "this_post_is_not_available_due_to_a_copyright_claim": + "Ця публікація недоступна через претензію на авторське право.", + "share_on_facebook": "Поширити у Facebook", + "share_on_twitter": "Поширити у Twitter", + "share_on_reddit": "Поширити у Reddit", + "share_on_linkedin": "Поширити у Linkedin", + "recent_password": "Останній пароль", + "in_week_convert_DEBT_TOKEN_to_LIQUID_TOKEN": + "За 3,5 дні конвертує %(amount)s %(DEBT_TOKEN)s в %(LIQUID_TOKEN)s", + "view_the_full_context": "Переглянути повний контекст", + "view_the_direct_parent": "Переглянути попереднє повідомлення", + "you_are_viewing_a_single_comments_thread_from": + "Ви переглядаєте унікальний коментар до публікації", + "recent_posts_by_author": "Recent Posts by %(author)s" + }, + "recoveraccountstep1_jsx": { + "recover_account_intro": + "Час від часу ключ власника Steemian може бути порушений. На відновлення вкраденого облікового запису надається 30 днів законному власникові рахунку, щоб відновити свій рахунок з моменту, коли злодій змінив свій власний ключ. Відновлення вкраденого облікового запису можна використовувати лише на %(APP_URL)s якщо власник облікового запису раніше вказав '%(APP_NAME)s' як свого довіреного облікового запису та дотримувався Загальних умов обслуговування %(APP_NAME)s's." + }, + "user_profile": { + "unknown_account": "Невідомий рахунок", + "user_hasnt_made_any_posts_yet": + "Виглядає так, що %(name)s ще не зробив(-ла) жодної публікації!", + "user_hasnt_started_bloggin_yet": + "Виглядає так, що %(name)s ще не почав(-ла) вести блог!", + "user_hasnt_followed_anything_yet": + "Виглядає так, що %(name)s не встиг(-ла) підписатись на жодного користувача! %(name)s А якщо нещодавно додали нових користувачів, яких почав(-ла) слідкувати, а їхня персоналізована срічка буде заповнена, коли з’явиться новий вміст.", + "user_hasnt_had_any_replies_yet": + "%(name)s ще не отримав(-ла) жодної відповіді", + "user_hasnt_had_any_notifications_yet": + "%(name)s hasn't had any notifications yet", + "looks_like_you_havent_posted_anything_yet": + "Схоже, ви ще нічого не опублікували.", + "create_a_post": "Створити допис", + "explore_trending_articles": "Ознайомтеся з тенденційними статтями", + "read_the_quick_start_guide": "Прочитайте Посібник із швидкого початку", + "browse_the_faq": "Browse The FAQ", + "followers": "Followers", + "this_is_users_reputations_score_it_is_based_on_history_of_votes": + "Це показник репутації %(name)s's.\n\nРейтинг репутації базується на історії голосів, отриманих за рахунок, і використовується для приховування контенту низької якості.", + "follower_count": { + "zero": "Жодниного послідовника", + "one": "1 послідовник", + "other": "%(count)s послідовники(-ів)" + }, + "followed_count": { + "zero": "Ні за ким не слідкує", + "one": "1 слідкує", + "other": "%(count)s слідкують" + }, + "post_count": { + "zero": "Жодного допису", + "one": "1 допис", + "other": "%(count)s дописів" + }, + "hide_reblogs": "Hide Reblogs", + "show_all": "Показати все", + "hide_resteems": "Сховати Рехайви" + }, + "post_jsx": { + "now_showing_comments_with_low_ratings": + "Зараз показуються коментарі з низькою оцінкою", + "sort_order": "Порядок сортування", + "comments_were_hidden_due_to_low_ratings": + "Коментарі були приховані через низькі оцінки", + "comment_sort_order": { + "trending": "Тенденції", + "votes": "Голоси", + "age": "Вік", + "reputation": "Репутація" + } + }, + "voting_jsx": { + "flagging_post_can_remove_rewards_the_flag_should_be_used_for_the_following": + "Позначення допису може видалити нагороди та зробити цей матеріал менш помітним. Деякі поширені причини прапора", + "disagreement_on_rewards": "Незгода на винагороду", + "fraud_or_plagiarism": "Шахрайство або плагіат", + "hate_speech_or_internet_trolling": + "Мова ненависті чи Інтернет-тролінг", + "intentional_miss_categorized_content_or_spam": + "Навмисна публікація єресі або спам", + "pending_payout": "Очікується сума виплати: $%(value)s", + "payout_declined": "Виплата відхилена", + "max_accepted_payout": "Макс. прийнята виплата: $%(value)s", + "promotion_cost": "Вартість просування $%(value)s", + "past_payouts": "Минулі виплати $%(value)s", + "past_payouts_author": " - Автор $%(value)s", + "past_payouts_curators": " - Куратори $%(value)s", + "removing_your_vote": "Видалення вашого голосу", + "removing_your_vote_will_reset_curation_rewards_for_this_post": + "Вилучення голосу призведе до скидання вашої винагороди за цю посаду", + "changing_to_an_upvote": "Changing to an Up-Vote", + "changing_to_an_upvote_will_reset_curation_rewards_for_this_post": + "Якщо змінити функцію Голоса, як дізлайк, то скидаються ваші винагороди за цю посаду", + "changing_to_a_downvote": "Змінити на Дізлайк", + "changing_to_a_downvote_will_reset_curation_rewards_for_this_post": + "Якщо змінити функцію Пониження голосу, ви скинете ваші винагороди за цю посаду", + "confirm_flag": "Підтвердити прапор", + "and_more": "і %(count)s більше", + "votes_plural": { + "one": "%(count)s голос", + "other": "%(count)s голоси" + }, + "voting_power": "Voting Power", + "estimated_vote": "Estimated Vote Value", + "must_reached_minimum_payout": + "(сума для виплати повинна досягати 0,02$)", + "payout": "Виплата", + "breakdown": "Усі виплати" + }, + "votesandcomments_jsx": { + "no_responses_yet_click_to_respond": + "Ще немає коментарів. Клацніть, щоб прокоментувати.", + "response_count_tooltip": { + "zero": "коментарів немає. Клацніть, щоб прокоментувати.", + "one": "1 коментар. Клацніть, щоб прокоментувати.", + "other": "%(count)s коментаря(-ів). Клацніть, щоб прокоментувати." + }, + "vote_count": { + "zero": "Жодного голосу", + "one": "1 голос", + "other": "%(count)s голоси(-ів)" + } + }, + "userkeys_jsx": { + "public": "Публічно", + "private": "Приватно", + "public_something_key": "Публічний %(key)s Ключ", + "private_something_key": "Приватний %(key)s Ключ", + "posting_key_is_required_it_should_be_different": + "Ключ Допису використовується для розміщення та голосування. Він повинен відрізнятися від активного та власницького ключа.", + "the_active_key_is_used_to_make_transfers_and_place_orders": + "Активний ключ використовується для здійснення переказів та розміщення замовлень на внутрішньому ринку.", + "the_owner_key_is_required_to_change_other_keys": + "Власницький ключ - це головний ключ для облікового запису, і він необхідний для зміни інших ключів.", + "the_private_key_or_password_should_be_kept_offline": + "Приватний ключ або пароль для власника ключа повинні зберігатися в режимі офлайн якомога безпечному місці.", + "the_memo_key_is_used_to_create_and_read_memos": + "Пам'ятковий ключ використовується для створення та читання нагадувань." + }, + "suggestpassword_jsx": { + "APP_NAME_cannot_recover_passwords_keep_this_page_in_a_secure_location": + "%(APP_NAME)s не вдається відновити паролі. Зберігайте цю сторінку в надійному місці, наприклад: пожежобезпечному сейфі чи просто сейфі.", + "APP_NAME_password_backup": "%(APP_NAME)s Резервне копіювання пароля", + "APP_NAME_password_backup_required": + "%(APP_NAME)s Резервне копіювання пароля (обов’язково)", + "after_printing_write_down_your_user_name": + "Після друку, запишіть своє ім’я користувача" + }, + "converttohive_jsx": { + "your_existing_DEBT_TOKEN_are_liquid_and_transferable": + "Your existing %(DEBT_TOKEN)s are liquid and transferable. Instead you may wish to trade %(DEBT_TOKEN)s directly in this site under %(link)s or transfer to an external market.", + "this_is_a_price_feed_conversion": + "This is a price feed conversion. The 3.5 day delay is necessary to prevent abuse from gaming the price feed average.", + "convert_to_LIQUID_TOKEN": "Convert to %(LIQUID_TOKEN)s", + "DEBT_TOKEN_will_be_unavailable": + "This action will take place 3.5 days from now and can not be canceled. These %(DEBT_TOKEN)s will immediately become unavailable." + }, + "tips_js": { + "liquid_token": + "Торгові токени, які можна перенести будь-де в будь-який час.
    %(LIQUID_TOKEN)s-и можна конвертувати в %(VESTING_TOKEN)s-и у процесі, який називається Сила Голосу.", + "influence_token": + "Токени впливу, які дають вам більше контролю над виплатами за допис та дозволяють заробляти на винагороді за курацію.", + "estimated_value": + "Орієнтовне значення базується на середньому значенні %(LIQUID_TOKEN)s-и у доларах США.", + "non_transferable": + "%(VESTING_TOKEN)s-и є непередаваним і вимагає 3 місяців (13 платежів), щоб перетворити назад у %(LIQUID_TOKEN)s-и.", + "converted_VESTING_TOKEN_can_be_sent_to_yourself_but_can_not_transfer_again": + "Конвертовано %(VESTING_TOKEN)s-и можна надіслати самому собі або комусь іншому, але не можна перенести знову без перетворення на %(LIQUID_TOKEN)s-и.", + "part_of_your_hive_power_is_currently_delegated": + "Part of %(user_name)s's HIVE POWER is currently delegated. Delegation is donated for influence or to help new users perform actions on Hive. Your delegation amount can fluctuate.", + "steem_engine_tokens": + "Steem Engine is a smart contracts side-chain platform for the Steem blockchain.", + "snax_token": + "Snax is a blockchain platform created for people to receive rewards for their social activity. It can be integrated with your Steem acount. Read more about Snax at Snax.One.", + "part_of_your_steem_power_is_currently_delegated": + "Частка %(user_name)s's STEEM POWER наразі делегована. Делегація надається за вплив або для допомоги новим користувачам у виконанні дій на Hive. Сума вашої делегації може коливатися." + }, + "promote_post_jsx": { + "promote_post": "Просувати Допис", + "spend_your_DEBT_TOKEN_to_advertise_this_post": + "Витратьте свої %(DEBT_TOKEN)s-и щоб рекламувати цю публікацію в розділі промоціонованого вмісту", + "you_successfully_promoted_this_post": + "Ви успішно просунули цю публікацію", + "this_post_was_hidden_due_to_low_ratings": + "Цей допис був прихований через низькі оцінки" + }, + "about_jsx": { + "about_app": "Про %(APP_NAME)s", + "about_app_details": + "%(APP_NAME)s це платформа соціальних медіа, де кожен отримує плату за створення та курації контенту. А саме наша соціальна мережа використовує надійну систему цифрових балів під назвою Steem, яка підтримує реальну цінність для цифрових винагород за рахунок виявлення ринкової ціни та ліквідності.", + "learn_more_at_app_url": "Дізнатись більше %(APP_URL)s", + "resources": "Ресурси" + }, + "markdownviewer_jsx": { + "images_were_hidden_due_to_low_ratings": + "Світлина була прихована через низьку оцінку." + }, + "postsummary_jsx": { + "reblogged": "reblogged", + "reblogged_by": "Reblogged by", + "crossposted": "cross-posted", + "crossposted_from": "Cross-posted from", + "reveal_it": "Виявити", + "adjust_your": "підкоригуйте свій", + "display_preferences": "налаштування відображення", + "create_an_account": "створити обліковку", + "to_save_your_preferences": "щоб зберегти свої вподобання", + "resteemed": "рехайвнуто", + "resteemed_by": "Рехайвнув(-ла)" + }, + "posts_index": { + "empty_feed_1": "Схоже, ви ще ні за ким не слідкуєте", + "empty_feed_2": + "Якщо ви нещодавно додали нових користувачів для перегляду, ваша персоналізована стрічка заповниться, коли у них з’явиться новий вміст", + "empty_feed_3": "Ознайомтеся з тенденційними дописів", + "empty_feed_4": "Прочитайте Посібник із швидкого початку", + "empty_feed_5": "Перегляньте поширені запитання", + "my_feed": "Моя стрічку", + "accountnames_feed": "%(account_name)s Стрічка" + }, + "transferhistoryrow_jsx": { + "to_savings": "to savings", + "from_savings": "from savings", + "cancel_transfer_from_savings": + "Cancel transfer from savings (request %(request_id)s)", + "stop_power_down": "Stop power down", + "withdraw_vesting": "Start power down of %(powerdown_vests)s STEEM", + "start_power_down_of": "Start power down of", + "receive_interest_of": "Receive interest of", + "bad_actor_caution": "Caution", + "bad_actor_explained": + "This is from an account that looks fraudulent. If you wish, you may temporarily reveal this memo which may contain dangerous links.", + "bad_actor_reveal_memo": "I understand the risk; show anyway.", + "from_negative_rep_user_caution": "Caution", + "from_negative_rep_user_explained": + "This is from an account with a bad reputation. If you wish, you may temporarily reveal this memo which may contain dangerous links.", + "from_negative_rep_user_reveal_memo": + "I understand the risk; show anyway.", + "claim_reward_balance": { + "three_rewards": + "Claim rewards: %(first_reward)s, %(second_reward)s and %(third_reward)s", + "two_rewards": + "Claim rewards: %(first_reward)s and %(second_reward)s", + "one_reward": "Claim rewards: %(reward)s" + }, + "interest": "Receive interest of %(interest)s", + "fill_convert_request": + "Fill convert request: %(amount_in)s for %(amount_out)s", + "fill_order": { + "filled_by_current_owner": + "Paid %(open_pays)s for %(current_pays)s", + "open_owner_filled_my_order": + "Paid %(current_pays)s for %(open_pays)s" + }, + "transfer_to_vesting": { + "from_self": { + "no_to": "Transfer %(amount)s to STEEM POWER", + "to_someone": "Transfer %(amount)s STEEM POWER to " + }, + "to_self": "Receive %(amount)s STEEM POWER from ", + "from_user_to_user": + " Transfer %(amount)s STEEM POWER from %(from)s to " + }, + "transfer": { + "from_self": { + "to_savings": "Transfer to savings %(amount)s to ", + "from_savings": "Transfer from savings %(amount)s to ", + "not_savings": "Transfer %(amount)s to " + }, + "to_self": { + "to_savings": "Receive from savings %(amount)s from ", + "from_savings": "Transfer from savings %(amount)s from ", + "not_savings": "Received %(amount)s from " + }, + "to_someone_from_someone": { + "to_savings": + "Transfer to savings %(amount)s from %(from)s to %(to)s", + "from_savings": + "Transfer from savings %(amount)s from %(from)s to %(to)s", + "not_savings": "Transfer %(amount)s from %(from)s to %(to)s" + } + }, + "author_reward": "Author reward: %(amount)s for ", + "curation_reward": "Curation reward: %(amount)s for ", + "comment_benefactor_reward": + "Comment benefactor reward: %(amount)s for ", + "staking_reward": "Staking reward: %(amount)s", + "mining_reward": "Mining reward: %(amount)s", + "request_id": "Request ID: %(request_id)s", + "stake_to": "Staked %(amount)s to ", + "stake_from": "Staked %(amount)s from ", + "unstake": "Started unstake of %(amount)s", + "cancel_unstake": "Cancelled unstaked on %(amount)s", + "issue_from": "Issued %(amount)s" + }, + "savingswithdrawhistory_jsx": { + "cancel_this_withdraw_request": "Cancel this withdraw request?", + "pending_savings_withdrawals": "PENDING SAVINGS WITHDRAWS", + "withdraw": "Withdraw %(amount)s", + "to": "to %(to)s", + "from_to": "from %(from)s to %(to)s" + }, + "explorepost_jsx": { + "copied": "Скопійовано!", + "copy": "КОПІЮВАТИ", + "alternative_sources": "Альтернативні джерела" + }, + "header_jsx": { + "home": "Дім", + "create_a_post": "Створити допис", + "change_account_password": "Змінити пароль облікового запису", + "create_account": "Створити рахунок", + "stolen_account_recovery": "Відновлення викраденого рахунку", + "people_following": "Люди, що стежать %(username)s", + "people_followed_by": "Люди стежели за %(username)s", + "curation_rewards_by": "Кураторські нагороди від %(username)s", + "author_rewards_by": "Авторські нагороди %(username)s", + "replies_to": "Відповіді на %(username)s", + "posts_by": "Posts by %(username)s", + "comments_by": "Коментарі від %(username)s" + }, + "loginform_jsx": { + "you_need_a_private_password_or_key": + "Вам потрібен приватний пароль або ключ (не Публічний ключ)", + "cryptography_test_failed": "Тест на криптографію не вдався", + "unable_to_log_you_in": + "Ми не зможемо увійти до вас за допомогою цього браузера.", + "the_latest_versions_of": "The latest versions of ", + "are_well_tested_and_known_to_work_with": + "добре перевірені і відомо, що вони працюють %(APP_URL)s.", + "due_to_server_maintenance": + "Завдяки сервісному обслуговуванню ми працюємо лише в режимі читання. Просимо вибачення за незручності.", + "login_to_vote": "Увійдіть, щоб голосувати", + "login_to_post": "Увійдіть, щоб написати допис", + "login_to_comment": "Увійдіть, щоб коментувати", + "posting": "Публікується", + "active_or_owner": "Активний або Власницький", + "this_password_is_bound_to_your_account_owner_key": + "Цей пароль пов'язаний з власницьким ключем вашого облікового запису і не може використовуватися для входу на цей сайт.", + "however_you_can_use_it_to": "Однак ви можете використовувати це для ", + "update_your_password": "оновити свій пароль", + "to_obtain_a_more_secure_set_of_keys": + "щоб отримати більш безпечний набір ключів.", + "this_password_is_bound_to_your_account_active_key": + "Цей пароль прив’язаний до активного ключа вашого облікового запису і не може використовуватися для входу на цю сторінку.", + "you_may_use_this_active_key_on_other_more": + "Ви можете використовувати цей активний ключ на інших захищених сторінках, таких як сторінки Гаманець або Маркет.", + "you_account_has_been_successfully_created": + "Ваш обліковку успішно створено!", + "you_account_has_been_successfully_recovered": + "Ваш обліковку успішно відновлено!", + "password_update_succes": + "Пароль для %(accountName)s було успішно оновлено", + "password_info": + "Цей пароль або приватний ключ було введено неправильно. Ймовірно, є помилка рукописного тексту або введення даних. Підказка: пароль або приватний ключ, згенерований Hive, ніколи не буде містити символів 0 (нуль), O (літери o), I (літери i) та l (малі регістри L).", + "enter_your_username": "Введіть своє ім’я користувача", + "password_or_wif": "Пароль або WIF", + "this_operation_requires_your_key_or_master_password": + "Ця операція вимагає вашого %(authType)s ключ або головний пароль.", + "keep_me_logged_in": "Тримайте мене в системі", + "amazing_community": "дивовижна спільнота", + "to_comment_and_reward_others": + " коментувати та винагороджувати інших.", + "signup_button": "Зареєструйтесь зараз, щоб заробити ", + "signup_button_emphasis": "БЕЗКОШТОВНІ STEEM!", + "sign_up_get_steem": "Зареєструйтесь зараз. Отримуйте STEEM!", + "returning_users": "Повернення користувачів: ", + "login_warning_title": "Увійти за допомогою ключа, який не є публічний", + "login_warning_body": + "Ви намагаєтесь використовувати ключ із більшою кількістю дозволів, ніж потрібно для щоденного використання Hive.blog. Оскільки ключі та паролі частіше піддаються крадіжкам, наполегливо рекомендуємо використовувати Ваш Дописовий ключ для входу.", + "continue_anyway": "Продовжуйте все одно", + "login_warning_link_text": + "Відкрийте мій гаманець Hive для доступу та перегляду мого Дописового ключа", + "join_our": "Приєднуйтесь до наших", + "sign_transfer": "Підпишіться, щоб завершити передачу", + "use_keychain": "Використовуйте розширення KEYCHAIN", + "more_login_methods": "more login methods" + }, + "chainvalidation_js": { + "account_name_should_not_be_empty": + "Ім'я облікового запису не повинно бути порожнім.", + "account_name_should_be_longer": + "Назва облікового запису повинна бути довшою.", + "account_name_should_be_shorter": + "Назва облікового запису повинна бути коротшою.", + "each_account_segment_should_start_with_a_letter": + "Кожен сегмент обліковки повинен починатися з літери.", + "each_account_segment_should_have_only_letters_digits_or_dashes": + "У кожному сегменті облікового запису повинні бути лише літери, цифри або тире.", + "each_account_segment_should_have_only_one_dash_in_a_row": + "Кожен сегмент обліковки повинен мати лише один тире в ряд.", + "each_account_segment_should_end_with_a_letter_or_digit": + "Кожен сегмент обліковки повинен закінчуватися літерою або цифрою.", + "each_account_segment_should_be_longer": + "Кожен сегмент обліковки повинен бути довшим.", + "verified_exchange_no_memo": "Ви повинні додати пам’ятку для переказу.", + "badactor": + "З обережністю надсилайте на цю обліковку. Будь ласка, перевірте правопис від можливого фішингу.", + "memo_has_privatekey": + "Будь ласка, не включайте те, що видається приватним ключем або паролем.", + "memo_is_privatekey": "Не використовуйте приватні ключі в примітках.", + "memo_is_password": "Не використовуйте паролі в примітках." + }, + "settings_jsx": { + "invalid_url": "Недійсна URL-адреса", + "name_is_too_long": "Ім'я задовге", + "name_must_not_begin_with": "Ім'я не повинно починатися з @", + "about_is_too_long": "Розділ про себе занадто довгий", + "location_is_too_long": "Локація занадто довга", + "website_url_is_too_long": "URL-адреса веб-сайту задовга", + "public_profile_settings": "Налаштування загальнодоступного профілю", + "witness_owner_is_too_long": "Witness owner username is too long", + "witness_description_is_too_long": "Witness description is too long", + "preferences": "Переваги", + "not_safe_for_work_nsfw_content": "Небезпечний для роботи (NSFW) вміст", + "always_hide": "Завжди ховати", + "always_warn": "Завжди попереджати", + "always_show": "Завжди показувати", + "choose_default_blog_payout": "Нагороди Блогу", + "choose_default_comment_payout": "Нагороди Коментарів", + "muted_users": "Заглушені користувачі", + "update": "Оновити", + "upload_image": "Завантажити світлину", + "uploading_image": "Завантаження світлини", + "profile_image_url": "URL-адреса світлини профілю", + "cover_image_url": "URL-адресу світлини обкладинки", + "profile_name": "Відображуване ім'я", + "profile_about": "Про мене", + "profile_location": "Місцезнаходження", + "profile_website": "Веб-сайт", + "profile_witness_owner": "Witness owner", + "profile_witness_description": "Witness description", + "saved": "Збережено!", + "choose_preferred_api_endpoint": "Choose Your Preferred API Node", + "default_beneficiaries": "Referral System", + "default_beneficiaries_enabled": "Use Default Beneficiaries", + "default_beneficiaries_disabled": "Opt-Out Referral System", + "advanced": "Advanced", + "api_endpoint_options": "API Endpoint Options", + "endpoint": "Endpoint", + "preferred": "Preferred?", + "remove": "Remove", + "add_api_endpoint": "Add API Endpoint", + "reset_endpoints": "Reset Endpoints", + "error_bad_url": + "This appears to be a bad URL, please check it and try again", + "error_bad_cookie": "Unable to get endpoints from cookie", + "error_already_exists": "This endpoint is already in the list", + "error_cant_remove_active": + "You can't remove the current preferred endpoint. Please select a new preferred endpoint first", + "error_cant_remove_all": + "You must have at least one endpoint in the list" + }, + "transfer_jsx": { + "amount_is_in_form": "Сума у формі 99999.999", + "insufficient_funds": "Недостатньо коштів", + "use_only_3_digits_of_precison": "Використовуйте лише 3 цифри точності", + "send_to_account": "Надіслати на рахунок", + "asset": "Актив", + "this_memo_is_private": "Ця примітка є приватною", + "this_memo_is_public": "Ця пам’ятка є загальнодоступною", + "convert_to_VESTING_TOKEN": "Конвертувати в %(VESTING_TOKEN)s-и", + "balance_subject_to_3_day_withdraw_waiting_period": + "Залишки за умови 3-денного зняття періоду очікування.", + "move_funds_to_another_account": + "Перемістіть кошти на інший рахунок Steem.", + "protect_funds_by_requiring_a_3_day_withdraw_waiting_period": + "Захистіть кошти, вимагаючи 3-денного періоду очікування зняття.", + "withdraw_funds_after_the_required_3_day_waiting_period": + "Вилучіть кошти після необхідного 3-денного періоду очікування.", + "from": "Від", + "to": "Кому", + "asset_currently_collecting": + "%(asset)s наразі збирає %(interest)s%% КВТ.", + "beware_of_spam_and_phishing_links": + "Остерігайтеся спаму та фішингових посилань у пам’ятках про передачу. Не відкривайте джерело від користувачів, яким ви не довіряєте. Не надайте свої приватні ключі жодним стороннім веб-сайтам.", + "transactions_make_take_a_few_minutes": + "Операції не відображатимуться, поки їх не підтвердять на блокчейні, що може зайняти кілька хвилин.", + "autocomplete_previous_transfers": "попередні перекази", + "autocomplete_user_following": "послідовники", + "confirm_transfer": "Переведення %(amount)s від %(from)s кому %(to)s?" + }, + "userwallet_jsx": { + "transfer": "Переведення", + "conversion_complete_tip": "Буде завершено %(date)s", + "in_conversion": "%(amount)s в конверсії", + "transfer_to_savings": "Перехід до заощаджень", + "power_up": "Збільшити Силу", + "power_down": "Зменшити Силу", + "market": "Маркет", + "wallet": "Wallet", + "steem_wallet": "Steem Wallet", + "convert_to_LIQUID_TOKEN": "Конвертувати в %(LIQUID_TOKEN)s-и", + "withdraw_LIQUID_TOKEN": "Вивести %(LIQUID_TOKEN)s-и", + "withdraw_DEBT_TOKENS": "Вивести %(DEBT_TOKENS)s-и", + "tokens_worth_about_1_of_LIQUID_TICKER": + "Токени вартістю близько $1,00 %(LIQUID_TICKER)s-и, наразі збирає %(sbdInterest)s-и%% КВТ.", + "tradeable_tokens_transferred": + "Торгові токени, які можна перенести будь-де в будь-який час.", + "savings": "ЗБЕРЕЖЕННЯ", + "estimated_account_value": "Орієнтовна вартість рахунку", + "next_power_down_is_scheduled_to_happen": + "Наступне зменшення сили планується відбутися", + "transfers_are_temporary_disabled": "Переведення тимчасово відключені.", + "history": "ІСТОРІЯ", + "redeem_rewards": "Отримати нагороди (перевести на баланс)", + "buy_hive_or_hive_power": "Buy HIVE or HIVE POWER", + "buy_steem_or_steem_power": "Купити STEEM або STEEM СИЛУ" + }, + "powerdown_jsx": { + "power_down": "Unstake", + "amount": "Amount", + "already_power_down": + "You are already unstaking %(AMOUNT)s %(LIQUID_TICKER)s (%(WITHDRAWN)s %(LIQUID_TICKER)s paid out so far). Note that if you change the unstake amount the payout schedule will reset.", + "locked_in_unstake": + "You have %(AMOUNT)s %(LIQUID_TICKER)s stake locked in pending unstakes.", + "delegating": + "You are delegating %(AMOUNT)s %(LIQUID_TICKER)s. That amount is locked up and not available to unstake until the delegation is removed and a full reward period has passed.", + "per_week": "That's ~%(AMOUNT)s %(LIQUID_TICKER)s per week.", + "warning": + "Leaving less than %(AMOUNT)s %(VESTING_TOKEN)s in your account is not recommended and can leave your account in a unusable state.", + "error": "Unable to unstake (ERROR: %(MESSAGE)s)" + }, + "checkloginowner_jsx": { + "your_password_permissions_were_reduced": + "Ваші дозволи паролів були зменшені", + "if_you_did_not_make_this_change": + "Якщо ви не робили цієї зміни, то будь ласка", + "ownership_changed_on": "Власник змінився ", + "deadline_for_recovery_is": "Кінцевий термін для відновлення є", + "i_understand_dont_show_again": "Я розумію, не показуй мені знову" + }, + "termsagree_jsx": { + "please_review": "Перегляньте наші умови, щоб продовжити", + "hi_user": "Вітаю, %(username)s!", + "blurb": + "Наші Загальні положення та умови встановлюють правила та рекомендації, які ви повинні прийняти та дотримуватися, щоб використовувати Hive, а наша Політика конфіденційності пояснює тип інформації, яку ми збираємо, і що ми робимо з нею. Не хвилюйтесь, ми не продаємо ваші дані нікому. Ми використовуємо його для задоволення юридичних вимог та для того, щоб зробити Hive кращим.", + "i_agree_to_hives": "I agree to Hive's", + "terms_of_service": "Умови обслуговування", + "privacy_policy": "Політика конфіденційності", + "continue": "Продовжувати", + "cancel": "Відмінити", + "i_agree_to_Hives": "Я погоджуюсь із Hive's" + }, + "confirmtransactionform_jsx": { + "confirm": "Підтвердіти %(transactionType)s" + }, + "modals_jsx": { + "your_transaction_failed": "Не вдалося обробити транзакцію", + "out_of_bandwidth_title": "Чому? Ви вичерпали ресурсні кредити", + "out_of_bandwidth_reason": + "Такі дії, як публікація та голосування, використовують обчислювальні ресурси, розміщення реальної вартості на членів спільноти, які керують блокчейн Steem для всіх.", + "out_of_bandwidth_reason_2": + "Щоб зберегти речі вільними, Steem інтелектуально розподіляє ресурсні кредити кожному користувачеві на основі їхніх запасів Steem Сила, які можна використовувати для подання обмеженої кількості безплатних транзакцій. Якщо користувачеві не вистачає ресурсних кредитів, їм потрібно буде зачекати, коли він поповниться, або придбати додаткову потужність Steem. Ця система визначає пріоритетність дій сумлінних членів громади, обмежуючи спам.", + "out_of_bandwidth_option_title": "Щоб продовжувати взаємодію на Hive.:", + "out_of_bandwidth_option_1": "Купуйте та утримуйте більше Steem Сили", + "out_of_bandwidth_option_2": + "Зачекайте, поки ваші ресурсні кредити поповняться", + "out_of_bandwidth_option_3": + "Зачекайте, поки використання мережі зменшиться" + }, + "sanitizedlink_jsx": { + "phishylink_caution": "Джерело приховано; спроба фішингу", + "phishylink_reveal": "показати джерело" + }, + "delegations_jsx": { + "delegations": "Delegations" + }, + "notificationslist_jsx": { + "mark_all_as_read": "Mark all as read", + "load_more": "Load more...", + "all": "All", + "replies": "Replies", + "follows": "Follows", + "resteems": "Resteems", + "upvotes": "Upvotes", + "mentions": "Mentions" + }, + "converttosteem_jsx": { + "your_existing_DEBT_TOKEN_are_liquid_and_transferable": + "Ваші %(DEBT_TOKEN)s-и є рідкими та передаваними. Натомість, якщо ви бажаєте торгувати %(DEBT_TOKEN)s безпосередньо можна на цьому веб-сайті %(link)s або скористуватись на зовнішнім ринком.", + "this_is_a_price_feed_conversion": + "Це конверсія ринкової ставки. Кінцевий термін на 3,5 дня необхідний, щоб уникнути зловживань тих, хто намагається зробити ставку на короткострокові зміни середнього ринкового курсу.", + "convert_to_LIQUID_TOKEN": "Convert to %(LIQUID_TOKEN)s", + "DEBT_TOKEN_will_be_unavailable": + "Ця дія відбуватиметься через 3,5 дня і не може бути скасована.Ці(-ей) %(DEBT_TOKEN)s-и негайно стануть недоступними." + } +} diff --git a/src/app/redux/AuthSaga.js b/src/app/redux/AuthSaga.js index f2745fc65..7614800f7 100644 --- a/src/app/redux/AuthSaga.js +++ b/src/app/redux/AuthSaga.js @@ -6,7 +6,6 @@ import { PrivateKey } from '@hiveio/hive-js/lib/auth/ecc'; import { getAccount } from 'app/redux/SagaShared'; import * as userActions from 'app/redux/UserReducer'; -import { ALLOW_MASTER_PW } from 'app/client_config'; // operations that require only posting authority export const postingOps = Set( @@ -152,9 +151,12 @@ export function* findSigningKey({ password, useHive, }) { + const allowMasterPw = yield select(state => + state.app.getIn(['hostConfig', 'ALLOW_MASTER_PW'], false) + ); let authTypes; if (postingOps.has(opType) && !needsActiveAuth) - authTypes = 'posting, active' + (ALLOW_MASTER_PW ? ', owner' : ''); + authTypes = 'posting, active' + (allowMasterPw ? ', owner' : ''); else authTypes = 'active, owner'; authTypes = authTypes.split(', '); diff --git a/src/app/redux/ChatSaga.js b/src/app/redux/ChatSaga.js index 3d7de947a..74cd4277a 100644 --- a/src/app/redux/ChatSaga.js +++ b/src/app/redux/ChatSaga.js @@ -1,4 +1,4 @@ -import { fromJS } from 'immutable'; +import { List, fromJS } from 'immutable'; import { Signature, hash } from '@hiveio/hive-js/lib/auth/ecc'; import { call, fork, put, select, take, takeEvery } from 'redux-saga/effects'; import { eventChannel } from 'redux-saga'; @@ -6,7 +6,6 @@ import { findSigningKey } from 'app/redux/AuthSaga'; import * as reducer from 'app/redux/ChatReducer'; import * as userActions from 'app/redux/UserReducer'; import { isLoggedInWithKeychain } from 'app/utils/SteemKeychain'; -import { CHAT_CONVERSATIONS } from 'app/client_config'; import axios from 'axios'; export const chatWatches = [ @@ -247,7 +246,8 @@ export function* logout() { export function* fetchChatList(action) { const chatList = yield authorizedCallChatApi('messages/conversations'); - const finalChatList = CHAT_CONVERSATIONS ? CHAT_CONVERSATIONS.concat(chatList) : chatList; + const conversations = yield select(state => state.app.getIn(['hostConfig', 'CHAT_CONVERSATIONS'])); + const finalChatList = conversations ? conversations.toJS().concat(chatList) : chatList; yield put( reducer.receiveChatList(finalChatList) ); diff --git a/src/app/redux/FetchDataSaga.js b/src/app/redux/FetchDataSaga.js index 6a52dab12..d121e8c90 100644 --- a/src/app/redux/FetchDataSaga.js +++ b/src/app/redux/FetchDataSaga.js @@ -25,15 +25,6 @@ import { getScotDataAsync, callBridge, } from 'app/utils/steemApi'; -import { - PREFER_HIVE, - HIVE_ENGINE, - LIQUID_TOKEN_UPPERCASE, - COMMUNITY_CATEGORY, - TAG_LIST, - APPEND_TRENDING_TAGS_COUNT, - TRENDING_TAGS_TO_IGNORE, -} from 'app/client_config'; import { fetchCrossPosts, augmentContentWithCrossPost, @@ -95,22 +86,25 @@ export function* fetchState(location_change_action) { } const { pathname } = location_change_action.payload; const m = pathname.match(/^\/@([a-z0-9\.-]+)(\/notifications)?/); + const hostConfig = yield select(state => + state.app.get('hostConfig', Map()).toJS() + ); if (m && m.length >= 2) { const username = m[1]; - yield fork(fetchFollowCount, username, PREFER_HIVE); + yield fork(fetchFollowCount, username, hostConfig['PREFER_HIVE']); yield fork( loadFollows, 'getFollowersAsync', username, 'blog', - PREFER_HIVE + hostConfig['PREFER_HIVE'] ); yield fork( loadFollows, 'getFollowingAsync', username, 'blog', - PREFER_HIVE + hostConfig['PREFER_HIVE'] ); } @@ -120,7 +114,7 @@ export function* fetchState(location_change_action) { pathname.indexOf('trending') !== -1 || pathname.indexOf('hot') !== -1 ) { - yield fork(getPromotedState, pathname); + yield fork(getPromotedState, pathname, hostConfig); } // `ignore_fetch` case should only trigger on initial page load. No need to call @@ -147,7 +141,7 @@ export function* fetchState(location_change_action) { } let url = `${pathname}`; - if (url === '/') url = `/trending`; + if (url === '/') url = hostConfig['DEFAULT_URL'] ? hostConfig['DEFAULT_URL'] : `/trending`; // Replace /curation-rewards and /author-rewards with /transfers for UserProfile // to resolve data correctly if (url.indexOf('/curation-rewards') !== -1) @@ -163,12 +157,18 @@ export function* fetchState(location_change_action) { state.user.getIn(['current', 'username']), ]); } - const state = yield call(getStateAsync, url, username, false); + const state = yield call( + getStateAsync, + url, + hostConfig, + username, + false + ); yield put(globalActions.receiveState(state)); yield call(fetchScotInfo); yield call(syncPinnedPosts); - if (COMMUNITY_CATEGORY) { - yield put(actions.getCommunity(COMMUNITY_CATEGORY)); + if (hostConfig['COMMUNITY_CATEGORY']) { + yield put(actions.getCommunity(hostConfig['COMMUNITY_CATEGORY'])); } } catch (error) { console.error('~~ Saga fetchState error ~~>', url, error); @@ -183,7 +183,7 @@ export function* fetchState(location_change_action) { * * @param {String} pathname */ -export function* getPromotedState(pathname) { +export function* getPromotedState(pathname, hostConfig) { const m = pathname.match(/^\/[a-z]*\/(.*)\/?/); const tag = m ? m[1] : ''; @@ -201,7 +201,7 @@ export function* getPromotedState(pathname) { state.user.getIn(['current', 'username']), ]); } - const state = yield call(getStateAsync, `/promoted/${tag}`, username, false); + const state = yield call(getStateAsync, `/promoted/${tag}`, hostConfig, username, false); yield put(globalActions.receiveState(state)); } @@ -209,9 +209,12 @@ function* syncPinnedPosts() { // Bail if we're rendering serverside since there is no localStorage if (!process.env.BROWSER) return null; + const scotTokenSymbol = yield select(state => + state.app.getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']) + ); // Get pinned posts from the store. const pinnedPosts = yield select(state => - state.offchain.get('pinned_posts') + state.offchain.getIn(['pinned_posts', scotTokenSymbol]) ); // Mark seen posts. @@ -279,7 +282,9 @@ function* getCommunitySaga() { function* fetchCommunity(tag) { const currentUser = yield select(state => state.user.get('current')); const currentUsername = currentUser && currentUser.get('username'); - const useHive = PREFER_HIVE; + const useHive = yield select(state => + state.app.getIn(['hostConfig', 'PREFER_HIVE'], false) + ); try { // TODO: If no current user is logged in, skip the observer param. @@ -306,20 +311,26 @@ function* fetchCommunity(tag) { } export function* getCategories(action) { + const hostConfig = yield select(state => + state.app.get('hostConfig', Map()).toJS() + ); + const APPEND_TRENDING_TAGS_COUNT = hostConfig['APPEND_TRENDING_TAGS_COUNT'] || 0; + const TRENDING_TAGS_TO_IGNORE = hostConfig['TRENDING_TAGS_TO_IGNORE'] || []; + if (APPEND_TRENDING_TAGS_COUNT === 0) { - yield put(globalActions.receiveCategories(TAG_LIST)); + yield put(globalActions.receiveCategories(hostConfig['TAG_LIST'])); return; } const trendingCategories = yield call( getScotDataAsync, 'get_trending_tags', { - token: LIQUID_TOKEN_UPPERCASE, + token: hostConfig['LIQUID_TOKEN_UPPERCASE'], } ); - const ignoreTags = new Set(TAG_LIST.concat(TRENDING_TAGS_TO_IGNORE)); + const ignoreTags = new Set(hostConfig['TAG_LIST'].concat(TRENDING_TAGS_TO_IGNORE)); const toAdd = trendingCategories.filter(c => !ignoreTags.has(c)).slice(0, APPEND_TRENDING_TAGS_COUNT); - yield put(globalActions.receiveCategories(TAG_LIST.concat(toAdd))); + yield put(globalActions.receiveCategories(hostConfig['TAG_LIST'].concat(toAdd))); } /** @@ -478,6 +489,9 @@ export function* fetchData(action) { let useBridge = false; + const hostConfig = yield select(state => + state.app.get('hostConfig', Map()).toJS() + ); yield put(globalActions.fetchingData({ order, category })); let call_name, args; if (order === 'trending') { @@ -608,6 +622,7 @@ export function* fetchData(action) { fetchFeedDataAsync, useHive, call_name, + hostConfig, args ); data = feedData; @@ -744,6 +759,9 @@ export function* getRewardsDataSaga(action) { function* getStakedAccountsSaga() { while (true) { const action = yield take(GET_STAKED_ACCOUNTS); + const scotTokenSymbol = yield select(state => + state.app.getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']) + ); const loadedStakedAccounts = yield select(state => state.global.has('stakedAccounts') ); @@ -751,7 +769,7 @@ function* getStakedAccountsSaga() { state.app.getIn(['scotConfig', 'info', 'precision'], 0) ); if (!loadedStakedAccounts) { - const params = { token: LIQUID_TOKEN_UPPERCASE }; + const params = { token: scotTokenSymbol }; try { const stakedAccounts = yield call( getScotDataAsync, @@ -772,8 +790,12 @@ function* getStakedAccountsSaga() { } function* fetchScotInfo() { + const hostConfig = yield select(state => + state.app.get('hostConfig', Map()).toJS() + ); + const scotTokenSymbol = hostConfig['LIQUID_TOKEN_UPPERCASE']; const scotInfo = yield call(getScotDataAsync, 'info', { - token: LIQUID_TOKEN_UPPERCASE, + token: scotTokenSymbol, }); yield put(appActions.receiveScotInfo(fromJS(scotInfo))); } @@ -796,10 +818,14 @@ function* fetchAuthorRecentPosts(action) { limit, } = action.payload; + const scotTokenSymbol = yield select(state => + state.app.getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']) + ); + const call_name = 'get_discussions_by_blog'; const args = { tag: author, - token: LIQUID_TOKEN_UPPERCASE, + token: scotTokenSymbol, limit: limit + 1, }; try { diff --git a/src/app/redux/FollowSaga.js b/src/app/redux/FollowSaga.js index 12d368442..1bfbafe02 100644 --- a/src/app/redux/FollowSaga.js +++ b/src/app/redux/FollowSaga.js @@ -71,7 +71,7 @@ function* loadFollowsLoop( params['following'] = account; } if (useHive) { - params['hive'] = '1'; + //params['hive'] = '1'; } const res = fromJS(yield call(getScotDataAsync, 'get_following', params)); // console.log('res.toJS()', res.toJS()) diff --git a/src/app/redux/GlobalReducer.js b/src/app/redux/GlobalReducer.js index aa5f6df86..790ef8eeb 100644 --- a/src/app/redux/GlobalReducer.js +++ b/src/app/redux/GlobalReducer.js @@ -187,8 +187,7 @@ export default function reducer(state = defaultState, action = {}) { const stakedAccounts = {}; payload.stakedAccounts.forEach(acc => { stakedAccounts[acc['name']] = - parseFloat(acc['staked_tokens']) / - Math.pow(10, payload.precision); + parseFloat(acc['staked_tokens']); }); return state.set('stakedAccounts', fromJS(stakedAccounts)); } diff --git a/src/app/redux/GlobalReducer.test.js b/src/app/redux/GlobalReducer.test.js index 4293edb09..cb47ac916 100644 --- a/src/app/redux/GlobalReducer.test.js +++ b/src/app/redux/GlobalReducer.test.js @@ -1,9 +1,6 @@ import { Map, OrderedMap, getIn, List, fromJS, Set, merge } from 'immutable'; import * as globalActions from './GlobalReducer'; import reducer, { defaultState } from './GlobalReducer'; -import { SCOT_DENOM } from 'app/client_config'; - -const FLAG_WEIGHT = 2 - Math.min(2, Math.log10(SCOT_DENOM)); describe('Global reducer', () => { it('should provide a nice initial state', () => { diff --git a/src/app/redux/SagaShared.js b/src/app/redux/SagaShared.js index be0ce80d0..ebcd4dd7c 100644 --- a/src/app/redux/SagaShared.js +++ b/src/app/redux/SagaShared.js @@ -76,11 +76,23 @@ function* showTransactionErrorNotification() { } export function* getContent({ author, permlink, resolve, reject }) { + const scotTokenSymbol = yield select(state => + state.app.getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']) + ); + const preferHive = yield select(state => + state.app.getIn(['hostConfig', 'PREFER_HIVE']) + ); let content; let attempt = 1; while (!content && attempt <= 5) { console.log('getContent', author, permlink); - content = yield call(getContentAsync, author, permlink); + content = yield call( + getContentAsync, + author, + permlink, + scotTokenSymbol, + preferHive + ); if (content['author'] == '') { // retry if content not found. #1870 attempt += 1; diff --git a/src/app/redux/TransactionSaga.js b/src/app/redux/TransactionSaga.js index c83fb9a6d..93a5732a6 100644 --- a/src/app/redux/TransactionSaga.js +++ b/src/app/redux/TransactionSaga.js @@ -14,12 +14,11 @@ import * as appActions from 'app/redux/AppReducer'; import * as globalActions from 'app/redux/GlobalReducer'; import * as transactionActions from 'app/redux/TransactionReducer'; import * as userActions from 'app/redux/UserReducer'; -import { APP_URL, COMMENT_FOOTER, POST_FOOTER } from 'app/client_config'; import { serverApiRecordEvent } from 'app/utils/ServerApiClient'; import { isLoggedInWithKeychain } from 'app/utils/SteemKeychain'; import SSC from '@hive-engine/sscjs'; -const steemSsc = new SSC('https://api.steem-engine.net/rpc'); +const steemSsc = new SSC('https://ha.herpc.dtools.dev'); const hiveSsc = new SSC('https://ha.herpc.dtools.dev'); import { callBridge } from 'app/utils/steemApi'; import { @@ -565,11 +564,14 @@ export function* preBroadcast_comment({ operation, username, useHive }) { if (!permlink) permlink = yield createPermlink(title, author, useHive); - const postUrl = `${APP_URL}/@${author}/${permlink}`; + const hostConfig = yield select(state => + state.app.get('hostConfig', Map()).toJS() + ); + const postUrl = `${hostConfig['APP_URL']}/@${author}/${permlink}`; // Add footer const footer = (parent_author === '' - ? POST_FOOTER - : COMMENT_FOOTER + ? hostConfig['POST_FOOTER'] + : hostConfig['COMMENT_FOOTER'] ).replace('${POST_URL}', postUrl); if (footer && !body.endsWith(footer)) { body += '\n\n' + footer; diff --git a/src/app/redux/TransactionSaga.test.js b/src/app/redux/TransactionSaga.test.js index 1272549c1..cc4e851a0 100644 --- a/src/app/redux/TransactionSaga.test.js +++ b/src/app/redux/TransactionSaga.test.js @@ -12,7 +12,6 @@ import { transactionWatches, broadcastOperation, } from './TransactionSaga'; -import { APP_URL, POST_FOOTER, DEBT_TICKER } from 'app/client_config'; import { configure, shallow } from 'enzyme'; import Adapter from 'enzyme-adapter-react-15'; @@ -42,9 +41,9 @@ const operation = { const username = 'Beatrice'; function addFooter(body, author, permlink) { - const footer = POST_FOOTER.replace( + const footer = 'test ${POST_URL} footer'.replace( '${POST_URL}', - `${APP_URL}/@${author}/${permlink}` + `appurl/@${author}/${permlink}` ); if (footer && !body.endsWith(footer)) { return body + '\n\n' + footer; @@ -125,7 +124,11 @@ describe('TransactionSaga', () => { expect(expected).toEqual(actual); }); it('should return the comment options array.', () => { - let actual = gen.next('mock-permlink-123').value; + gen.next('mock-permlink-123'); + let actual = gen.next({ + POST_FOOTER: 'test ${POST_URL} footer', + APP_URL: 'appurl', + }).value; const expected = [ [ 'comment', @@ -166,7 +169,12 @@ describe('TransactionSaga', () => { operation.parent_author, operation.parent_permlink ); - const actual = gen.next('mock-permlink-123').value; + + gen.next('mock-permlink-123'); + const actual = gen.next({ + POST_FOOTER: 'test ${POST_URL} footer', + APP_URL: 'appurl', + }).value; const expected = createPatch( originalBod, addFooter(operation.body, operation.author, 'mock-permlink-123') @@ -183,7 +191,12 @@ describe('TransactionSaga', () => { operation.parent_author, operation.parent_permlink ); - const actual = gen.next('mock-permlink-123').value; + + gen.next('mock-permlink-123'); + const actual = gen.next({ + POST_FOOTER: 'test ${POST_URL} footer', + APP_URL: 'appurl', + }).value; const expected = addFooter( operation.body, operation.author, diff --git a/src/app/redux/UserProfilesSaga.js b/src/app/redux/UserProfilesSaga.js index 09e48e6bd..e13280ac3 100644 --- a/src/app/redux/UserProfilesSaga.js +++ b/src/app/redux/UserProfilesSaga.js @@ -1,4 +1,4 @@ -import { call, put, takeLatest } from 'redux-saga/effects'; +import { call, put, select, takeLatest } from 'redux-saga/effects'; import * as userProfileActions from './UserProfilesReducer'; import { getAccount, getWalletAccount } from 'app/utils/steemApi'; @@ -21,7 +21,13 @@ export function* fetchUserProfile(action) { export function* fetchWalletUserProfile(action) { const { account } = action.payload; - const ret = yield call(getWalletAccount, account, true); + const scotTokenSymbol = yield select(state => + state.app.getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']) + ); + const useHive = yield select(state => + state.app.getIn(['hostConfig', 'HIVE_ENGINE']) + ); + const ret = yield call(getWalletAccount, account, useHive, scotTokenSymbol); if (!ret) throw new Error('Account not found'); yield put( userProfileActions.addProfile({ username: account, account: ret }) diff --git a/src/app/redux/UserSaga.js b/src/app/redux/UserSaga.js index f2ee1b439..41e959521 100644 --- a/src/app/redux/UserSaga.js +++ b/src/app/redux/UserSaga.js @@ -4,12 +4,6 @@ import { api as steemApi, auth as steemAuth } from '@steemit/steem-js'; import { api as hiveApi, auth as hiveAuth } from '@hiveio/hive-js'; import { PrivateKey, Signature, hash } from '@hiveio/hive-js/lib/auth/ecc'; -import { - ALLOW_MASTER_PW, - LIQUID_TOKEN_UPPERCASE, - HIVE_ENGINE, - PREFER_HIVE, -} from 'app/client_config'; import { accountAuthLookup } from 'app/redux/AuthSaga'; import { logout as chatLogout } from 'app/redux/ChatSaga'; import { getAccount } from 'app/redux/SagaShared'; @@ -34,7 +28,7 @@ import DMCAUserList from 'app/utils/DMCAUserList'; import SSC from '@hive-engine/sscjs'; import { getScotAccountDataAsync } from 'app/utils/steemApi'; -const steemSsc = new SSC('https://api.steem-engine.net/rpc'); +const steemSsc = new SSC('https://ha.herpc.dtools.dev'); const hiveSsc = new SSC('https://ha.herpc.dtools.dev'); import { @@ -115,7 +109,13 @@ function* shouldShowLoginWarning({ username, password }, useHive) { } // If it's a master key, show the warning. - if (!ALLOW_MASTER_PW && !(useHive ? hiveAuth : steemAuth).isWif(password)) { + const allowMasterPassword = yield select(state => + state.app.getIn(['hostConfig', 'ALLOW_MASTER_PW']) + ); + if ( + !allowMasterPassword && + !(useHive ? hiveAuth : steemAuth).isWif(password) + ) { const account = (yield (useHive ? hiveApi : steemApi).getAccountsAsync([ username, ]))[0]; @@ -143,7 +143,9 @@ function* shouldShowLoginWarning({ username, password }, useHive) { owner, posting keys. */ function* checkKeyType(action) { - const useHive = PREFER_HIVE; + const useHive = yield select(state => + state.app.getIn(['hostConfig', 'PREFER_HIVE']) + ); if (yield call(shouldShowLoginWarning, action.payload, useHive)) { yield put(userActions.showLoginWarning(action.payload)); } else { @@ -177,7 +179,9 @@ function* usernamePasswordLogin(action) { // take a while on slow computers. yield call(usernamePasswordLogin2, action.payload); const current = yield select(state => state.user.get('current')); - const useHive = PREFER_HIVE; + const useHive = yield select(state => + state.app.getIn(['hostConfig', 'PREFER_HIVE']) + ); if (current) { const username = current.get('username'); yield fork(loadFollows, 'getFollowingAsync', username, 'blog', useHive); @@ -208,6 +212,9 @@ function* usernamePasswordLogin2({ operationType /*high security*/, afterLoginRedirectToWelcome, }) { + const hostConfig = yield select(state => + state.app.get('hostConfig', Map()).toJS() + ); const user = yield select(state => state.user); const loginType = user.get('login_type'); const justLoggedIn = loginType === 'basic'; @@ -277,7 +284,7 @@ function* usernamePasswordLogin2({ const isRole = (role, fn) => !userProvidedRole || role === userProvidedRole ? fn() : undefined; - const account = yield call(getAccount, username, PREFER_HIVE); + const account = yield call(getAccount, username, hostConfig['PREFER_HIVE']); if (!account) { console.log('No account'); yield put(userActions.loginError({ error: 'Username does not exist' })); @@ -292,14 +299,15 @@ function* usernamePasswordLogin2({ return; } // fetch SCOT stake - const ssc = HIVE_ENGINE ? hiveSsc : steemSsc; + const scotTokenSymbol = hostConfig['LIQUID_TOKEN_UPPERCASE']; + const ssc = hostConfig['HIVE_ENGINE'] ? hiveSsc : steemSsc; const token_balances = yield call( [ssc, ssc.findOne], 'tokens', 'balances', { account: username, - symbol: LIQUID_TOKEN_UPPERCASE, + symbol: scotTokenSymbol, } ); //check for defaultBeneficiaries @@ -400,7 +408,7 @@ function* usernamePasswordLogin2({ account, private_keys, login_owner_pubkey, - useHive: PREFER_HIVE, + useHive: hostConfig['PREFER_HIVE'], }, }); let authority = yield select(state => @@ -417,7 +425,8 @@ function* usernamePasswordLogin2({ } const hasOwnerAuth = authority.get('owner') === 'full'; - if (!ALLOW_MASTER_PW && hasOwnerAuth) { + const allowMasterPassword = hostConfig['ALLOW_MASTER_PW']; + if (!allowMasterPassword && hasOwnerAuth) { console.log('Rejecting due to detected owner auth'); yield put(userActions.loginError({ error: 'owner_login_blocked' })); return; @@ -433,7 +442,7 @@ function* usernamePasswordLogin2({ localStorage.removeItem('autopost2'); const owner_pub_key = account.getIn(['owner', 'key_auths', 0, 0]); if ( - !ALLOW_MASTER_PW && + !allowMasterPassword && (login_owner_pubkey === owner_pub_key || login_wif_owner_pubkey === owner_pub_key) ) { @@ -556,7 +565,7 @@ function* usernamePasswordLogin2({ const challenge = { token: challengeString }; const buf = JSON.stringify(challenge, null, 0); const bufSha = hash.sha256(buf); - const useHive = PREFER_HIVE; + const useHive = hostConfig['PREFER_HIVE']; if (useKeychain) { const response = yield new Promise(resolve => { @@ -740,8 +749,11 @@ function* saveLogin_localStorage() { } // Save the lowest security key, or owner if allowed + const allowMasterPassword = yield select(state => + state.app.getIn(['hostConfig', 'ALLOW_MASTER_PW']) + ); let posting_private = private_keys && private_keys.get('posting_private'); - if (private_keys && !posting_private && ALLOW_MASTER_PW) { + if (private_keys && !posting_private && allowMasterPassword) { posting_private = private_keys.get('owner_private'); } @@ -764,7 +776,7 @@ function* saveLogin_localStorage() { if (auth.get(0) === postingPubkey) throw 'Login will not be saved, posting key is the same as active key'; }); - if (!ALLOW_MASTER_PW) { + if (!allowMasterPassword) { account.getIn(['owner', 'key_auths']).forEach(auth => { if (auth.get(0) === postingPubkey) throw 'Login will not be saved, posting key is the same as owner key'; @@ -880,15 +892,18 @@ function* uploadImage({ let sig; let postUrl; + const useHive = yield select(state => + state.app.getIn(['hostConfig', 'PREFER_HIVE']) + ); if (hiveSignerLogin) { // verify user with access_token for HiveSigner login - postUrl = `${$STM_Config.upload_image}/hs/${ + postUrl = `${$STM_Config.hive_upload_image}/hs/${ hiveSignerClient.accessToken }`; } else { if (keychainLogin) { const response = yield new Promise(resolve => { - (PREFER_HIVE + (useHive ? window.hive_keychain : window.steem_keychain ).requestSignBuffer( @@ -909,7 +924,11 @@ function* uploadImage({ } else { sig = Signature.signBufferSha256(bufSha, d).toHex(); } - postUrl = `${$STM_Config.upload_image}/${username}/${sig}`; + + const baseUploadUrl = useHive + ? $STM_Config.hive_upload_image + : $STM_Config.upload_image; + postUrl = `${baseUploadUrl}/${username}/${sig}`; } const xhr = new XMLHttpRequest(); @@ -954,18 +973,17 @@ function* uploadImage({ function* lookupVotingPower({ payload: { account } }) { const accountData = yield call(getScotAccountDataAsync, account); + const scotTokenSymbol = yield select(state => + state.app.getIn(['hostConfig', 'LIQUID_TOKEN_UPPERCASE']) + ); + const rewardPoolId = yield select(state => + state.app.getIn(['hostConfig', 'HIVE_ENGINE_SMT']) + ); yield put( userActions.setVotingPower({ account, - ...accountData.data[LIQUID_TOKEN_UPPERCASE], + ...accountData.data[rewardPoolId], + staked_tokens: accountData.tokenData[scotTokenSymbol], }) ); - if (accountData.hiveData) { - yield put( - userActions.setHiveVotingPower({ - account, - ...accountData.hiveData[LIQUID_TOKEN_UPPERCASE], - }) - ); - } } diff --git a/src/app/utils/AffiliationMap.js b/src/app/utils/AffiliationMap.js index 1e05e95cb..2a4e45e06 100644 --- a/src/app/utils/AffiliationMap.js +++ b/src/app/utils/AffiliationMap.js @@ -1,4 +1,5 @@ const map = { + //steemit elipowell: 'Steemit', steemitblog: 'Steemit', @@ -10,9 +11,19 @@ const map = { // 'robot.pay' : 'Robot', }; -export function affiliationFromStake(accountName, stake) { +export function getAffiliation(token, user) { + if (map[token] && map[token][user]) { + return map[token][user]; + } else if (map[user]) { + return map[user]; + } else { + return ''; + } +} + +export function affiliationFromStake(token, accountName, stake) { // Put stake based breakdowns here. - return map[accountName]; + return getAffiliation(token, accountName); } export default map; diff --git a/src/app/utils/CanonicalLinker.js b/src/app/utils/CanonicalLinker.js index d9518f4e0..3ccc2fcf7 100644 --- a/src/app/utils/CanonicalLinker.js +++ b/src/app/utils/CanonicalLinker.js @@ -1,5 +1,4 @@ import Apps from '@hiveio/hivescript/apps.json'; -import { APP_URL } from 'app/client_config'; function read_md_app(metadata) { return metadata && @@ -33,7 +32,6 @@ function build_scheme(scheme, post) { export function makeCanonicalLink(post, metadata) { let scheme; - if (metadata) { const canonUrl = read_md_canonical(metadata); if (canonUrl) return canonUrl; diff --git a/src/app/utils/CanonicalLinker.test.js b/src/app/utils/CanonicalLinker.test.js index b38c67abf..c525c30cd 100644 --- a/src/app/utils/CanonicalLinker.test.js +++ b/src/app/utils/CanonicalLinker.test.js @@ -1,7 +1,5 @@ import { makeCanonicalLink } from './CanonicalLinker'; -import { APP_NAME, APP_URL, PREFER_HIVE } from 'app/client_config'; - -const DEFAULT_URL = PREFER_HIVE ? 'https://hive.blog' : 'https://steemit.com'; +const DEFAULT_URL = 'https://hive.blog'; describe('makeCanonicalLink', () => { const post_data = { @@ -46,6 +44,14 @@ describe('makeCanonicalLink', () => { { ...post_data, json_metadata: { app: 'hiveblog/0.1' } }, 'https://hive.blog/testing/@test/test-post', ], + [ + 'handles posts from app', + { + ...post_data, + json_metadata: { app: `appname/0.1` }, + }, + `${DEFAULT_URL}/testing/@test/test-post`, + ], [ 'handles badly formatted app strings', { ...post_data, json_metadata: { app: 'fakeapp/0.0.1/a////' } }, diff --git a/src/app/utils/ExtractContent.js b/src/app/utils/ExtractContent.js index e20b9c7bf..67a71ee79 100644 --- a/src/app/utils/ExtractContent.js +++ b/src/app/utils/ExtractContent.js @@ -12,20 +12,24 @@ const getValidImage = array => { return array && Array.isArray(array) && array.length >= 1 && typeof array[0] === 'string' ? array[0] : null; }; -export function extractRtags(body = null) { +export function extractRtags(appDomain, hive, body = null) { let rtags; { const isHtml = /^([\S\s]*)<\/html>$/.test(body); const htmlText = isHtml ? body : remarkable.render(body.replace(/|$)/g, '(html comment removed: $1)')); - rtags = HtmlReady(htmlText, { mutate: false }); + rtags = HtmlReady(htmlText, { + appDomain, + useHive: hive, + mutate: false + }); } return rtags; } -export function extractImageLink(json_metadata, body = null) { +export function extractImageLink(json_metadata, appDomain, hive, body = null) { let json = Iterable.isIterable(json_metadata) ? json_metadata.toJS() : json_metadata; @@ -49,7 +53,7 @@ export function extractImageLink(json_metadata, body = null) { // If nothing found in json metadata, parse body and check images/links if (!image_link) { - const rtags = extractRtags(body); + const rtags = extractRtags(appDomain, hive, body); if (rtags.images) { [image_link] = Array.from(rtags.images); diff --git a/src/app/utils/ExtractMeta.js b/src/app/utils/ExtractMeta.js index b937ab040..90e2d0793 100644 --- a/src/app/utils/ExtractMeta.js +++ b/src/app/utils/ExtractMeta.js @@ -1,47 +1,62 @@ import { extractBodySummary, extractImageLink } from 'app/utils/ExtractContent'; import { makeCanonicalLink } from 'app/utils/CanonicalLinker.js'; -import { - APP_NAME, - APP_URL, - APP_ICON, - SITE_DESCRIPTION, - TWITTER_HANDLE, -} from 'app/client_config'; import { proxifyImageUrl } from 'app/utils/ProxifyUrl'; -const proxify = (url, size) => proxifyImageUrl(url, size).replace(/ /g, '%20'); +const proxify = (url, size) => proxifyImageUrl(url, true, size).replace(/ /g, '%20'); -function addSiteMeta(metas) { - metas.push({ title: APP_NAME }); - metas.push({ name: 'description', content: SITE_DESCRIPTION }); +function addSiteMeta(metas, hostConfig) { + metas.push({ title: hostConfig['APP_NAME'] }); + metas.push({ + name: 'description', + content: hostConfig['SITE_DESCRIPTION'], + }); metas.push({ property: 'og:type', content: 'website' }); - metas.push({ property: 'og:site_name', content: APP_NAME }); - metas.push({ property: 'og:title', content: APP_NAME }); - metas.push({ property: 'og:description', content: SITE_DESCRIPTION }); + metas.push({ property: 'og:site_name', content: hostConfig['APP_NAME'] }); + metas.push({ property: 'og:title', content: hostConfig['APP_NAME'] }); + metas.push({ + property: 'og:description', + content: hostConfig['SITE_DESCRIPTION'], + }); metas.push({ property: 'og:image', - content: `${APP_URL}/images/${APP_ICON}.png`, + content: `${hostConfig['APP_URL']}/images/${ + hostConfig['APP_ICON'] + }.png`, }); metas.push({ property: 'fb:app_id', content: $STM_Config.fb_app }); metas.push({ name: 'twitter:card', content: 'summary' }); - metas.push({ name: 'twitter:site', content: TWITTER_HANDLE }); - metas.push({ name: 'twitter:title', content: `#${APP_NAME}` }); - metas.push({ name: 'twitter:description', SITE_DESCRIPTION }); + metas.push({ + name: 'twitter:title', + content: `#${hostConfig['APP_NAME']}`, + }); + metas.push({ + name: 'twitter:description', + content: hostConfig['SITE_DESCRIPTION'], + }); metas.push({ name: 'twitter:image', - content: `${APP_URL}/images/${APP_ICON}.png`, + content: `${hostConfig['APP_URL']}/images/${ + hostConfig['APP_ICON'] + }.png`, }); } -function addPostMeta(metas, content, profile) { +function addPostMeta(metas, content, profile, hostConfig) { + const { + APP_NAME, + APP_DOMAIN, + APP_URL, + APP_ICON, + TWITTER_HANDLE, + } = hostConfig; const { profile_image } = profile; - const { category, created, body, json_metadata } = content; + const { category, created, body, json_metadata, hive } = content; const isReply = content.depth > 0; const title = content.title + ` — ${APP_NAME}`; const desc = extractBodySummary(body, isReply) + ' by ' + content.author; - const image_link = extractImageLink(json_metadata, body); + const image_link = extractImageLink(json_metadata, APP_DOMAIN, hive, body); const canonicalUrl = makeCanonicalLink(content, json_metadata); const localUrl = makeCanonicalLink(content, null); @@ -85,7 +100,8 @@ function addPostMeta(metas, content, profile) { }); } -function addAccountMeta(metas, accountname, profile) { +function addAccountMeta(metas, accountname, profile, hostConfig) { + const { SITE_DESCRIPTION, APP_URL, APP_ICON, TWITTER_HANDLE } = hostConfig; let { name, about, profile_image } = profile; name = name || accountname; @@ -114,7 +130,7 @@ function readProfile(chain_data, account) { return chain_data.profiles[account]['metadata']['profile']; } -export default function extractMeta(chain_data, rp) { +export default function extractMeta(chain_data, rp, hostConfig) { let username; let content; if (rp.username && rp.slug) { @@ -131,11 +147,11 @@ export default function extractMeta(chain_data, rp) { const metas = []; if (content) { - addPostMeta(metas, content, profile); + addPostMeta(metas, content, profile, hostConfig); } else if (username) { - addAccountMeta(metas, username, profile); + addAccountMeta(metas, username, profile, hostConfig); } else { - addSiteMeta(metas); + addSiteMeta(metas, hostConfig); } return metas; diff --git a/src/app/utils/FormatCoins.js b/src/app/utils/FormatCoins.js deleted file mode 100644 index d8545865b..000000000 --- a/src/app/utils/FormatCoins.js +++ /dev/null @@ -1,27 +0,0 @@ -import { - APP_NAME, - LIQUID_TOKEN, - LIQUID_TOKEN_UPPERCASE, - DEBT_TOKEN, - DEBT_TOKEN_SHORT, - CURRENCY_SIGN, - VESTING_TOKEN, -} from 'app/client_config'; - -// TODO add comments and explanations -// TODO change name to formatCoinTypes? -// TODO make use of DEBT_TICKER etc defined in config/clietn_config -export function formatCoins(string) { - // return null or undefined if string is not provided - if (!string) return string; - // TODO use .to:owerCase() ? for string normalisation - string = string - .replace('HBD', DEBT_TOKEN_SHORT) - .replace('HD', DEBT_TOKEN_SHORT) - .replace('Hive Power', VESTING_TOKEN) - .replace('HIVE POWER', VESTING_TOKEN) - .replace('Hive', LIQUID_TOKEN) - .replace('HIVE', LIQUID_TOKEN_UPPERCASE) - .replace('$', CURRENCY_SIGN); - return string; -} diff --git a/src/app/utils/JsPlugins.js b/src/app/utils/JsPlugins.js index eaf8c93d0..9d5bbb0d0 100644 --- a/src/app/utils/JsPlugins.js +++ b/src/app/utils/JsPlugins.js @@ -1,6 +1,6 @@ // 3rd party plugins -export default function init(config) { +export default function init(config, hostConfig) { // Google Analytics if (config.google_analytics_id) { (function(i, s, o, g, r, a, m) { @@ -29,14 +29,18 @@ export default function init(config) { }); } // Google Site Tag - if (config.gtag_measurement_id) { + let gtagMeasurementId = hostConfig.SDC_GTAG_MEASUREMENT_ID; + if (!gtagMeasurementId) { + gtagMeasurementId = config.gtag_measurement_id; + } + if (gtagMeasurementId) { (function(i, s, o, g, r, a, m) { i[r] = i[r] || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); - gtag('config', config.gtag_measurement_id); + gtag('config', gtagMeasurementId); (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]); a.async = 1; a.src = g; @@ -45,9 +49,7 @@ export default function init(config) { window, document, 'script', - `https://www.googletagmanager.com/gtag/js?id=${ - config.gtag_measurement_id - }`, + `https://www.googletagmanager.com/gtag/js?id=${gtagMeasurementId}`, 'dataLayer' ); } diff --git a/src/app/utils/Links.js b/src/app/utils/Links.js index d50febf52..cb0d37c30 100644 --- a/src/app/utils/Links.js +++ b/src/app/utils/Links.js @@ -1,5 +1,4 @@ import { PARAM_VIEW_MODE, VIEW_MODE_WHISTLE } from '../../shared/constants'; -import { APP_DOMAIN } from 'app/client_config'; const urlChar = '[^\\s"<>\\]\\[\\(\\)]'; const urlCharEnd = urlChar.replace(/\]$/, ".,']"); // insert bad chars to end on @@ -19,16 +18,9 @@ const urlSet = ({ domain = domainPath, path } = {}) => { Unless your using a 'g' (glob) flag you can store and re-use your regular expression. Use the cache below. If your using a glob (for example: replace all), the regex object becomes stateful and continues where it left off when called with the same string so naturally the regexp object can't be cached for long. */ export const any = (flags = 'i') => new RegExp(urlSet(), flags); -export const local = (flags = 'i') => +export const local = (flags = 'i') => appDomain => new RegExp( - urlSet({ domain: `(?:localhost|(?:.*\\.)?${APP_DOMAIN})` }), - flags - ); -export const remote = (flags = 'i') => - new RegExp( - urlSet({ - domain: `(?!localhost|(?:.*\\.)?${APP_DOMAIN})${domainPath}`, - }), + urlSet({ domain: `(?:localhost|(?:.*\\.)?${appDomain})` }), flags ); export const image = (flags = 'i') => @@ -40,7 +32,6 @@ export const imageFile = (flags = 'i') => new RegExp(imagePath, flags); export default { any: any(), local: local(), - remote: remote(), image: image(), imageFile: imageFile(), }; diff --git a/src/app/utils/Links.test.js b/src/app/utils/Links.test.js index 53362ca67..95ad70f05 100644 --- a/src/app/utils/Links.test.js +++ b/src/app/utils/Links.test.js @@ -1,7 +1,6 @@ import assert from 'assert'; import secureRandom from 'secure-random'; import links, * as linksRe from 'app/utils/Links'; -import { APP_DOMAIN } from 'app/client_config'; import youtubeRegex from 'app/components/elements/EmbeddedPlayers/youtube'; import threespeakRegex from 'app/components/elements/EmbeddedPlayers/threespeak'; import twitterRegex from 'app/components/elements/EmbeddedPlayers/twitter'; @@ -16,7 +15,11 @@ import { PARAM_VIEW_MODE, VIEW_MODE_WHISTLE } from '../../shared/constants'; describe('Links', () => { it('all', () => { - match(linksRe.any(), "https://example.com/wiki/Poe's_law", "https://example.com/wiki/Poe's_law"); + match( + linksRe.any(), + "https://example.com/wiki/Poe's_law", + "https://example.com/wiki/Poe's_law" + ); match(linksRe.any(), "https://example.com'", 'https://example.com'); match(linksRe.any(), '"https://example.com', 'https://example.com'); match(linksRe.any(), 'https://example.com"', 'https://example.com'); @@ -27,9 +30,17 @@ describe('Links', () => { match(linksRe.any(), ' https://example.com ', 'https://example.com'); match(linksRe.any(), 'https://example.com ', 'https://example.com'); match(linksRe.any(), 'https://example.com.', 'https://example.com'); - match(linksRe.any(), 'https://example.com/page.', 'https://example.com/page'); + match( + linksRe.any(), + 'https://example.com/page.', + 'https://example.com/page' + ); match(linksRe.any(), 'https://example.com,', 'https://example.com'); - match(linksRe.any(), 'https://example.com/page,', 'https://example.com/page'); + match( + linksRe.any(), + 'https://example.com/page,', + 'https://example.com/page' + ); }); it('multiple matches', () => { const all = linksRe.any('ig'); @@ -41,15 +52,13 @@ describe('Links', () => { it('by domain', () => { const locals = [ 'https://localhost/', - `http://${APP_DOMAIN}`, - `http://${APP_DOMAIN}/group`, + `http://appdomain`, + `http://appdomain/group`, ]; - match(linksRe.local(), locals); - matchNot(linksRe.remote(), locals); + match(linksRe.local()('appdomain'), locals); - const remotes = ['https://steemit.com/', 'http://abc.co']; - match(linksRe.remote(), remotes); - matchNot(linksRe.local(), remotes); + const remotes = ['https://example.com/', 'http://abc.co']; + matchNot(linksRe.local()('appdomain'), remotes); }); it('by image', () => { match(linksRe.image(), 'https://example.com/a.jpeg'); @@ -64,9 +73,20 @@ describe('Links', () => { ' { it('creates an empty string when there are no params', () => { assert(linksRe.makeParams([]) === '', 'not empty on array'); assert(linksRe.makeParams({}) === '', 'not empty on object'); - assert(linksRe.makeParams({}, false) === '', 'not empty on object with prefix false'); - assert(linksRe.makeParams([], false) === '', 'not empty on array with prefix false'); - assert(linksRe.makeParams([], '?') === '', 'not empty on array with prefix string'); - assert(linksRe.makeParams({}, '?') === '', 'not empty on object with prefix string'); + assert( + linksRe.makeParams({}, false) === '', + 'not empty on object with prefix false' + ); + assert( + linksRe.makeParams([], false) === '', + 'not empty on array with prefix false' + ); + assert( + linksRe.makeParams([], '?') === '', + 'not empty on array with prefix string' + ); + assert( + linksRe.makeParams({}, '?') === '', + 'not empty on object with prefix string' + ); }); it('creates the correct string when passed an array', () => { - assert(linksRe.makeParams(['bop=boop', 'troll=bridge']) === '?bop=boop&troll=bridge', 'incorrect string with'); assert( - linksRe.makeParams(['bop=boop', 'troll=bridge'], false) === 'bop=boop&troll=bridge', + linksRe.makeParams(['bop=boop', 'troll=bridge']) === + '?bop=boop&troll=bridge', + 'incorrect string with' + ); + assert( + linksRe.makeParams(['bop=boop', 'troll=bridge'], false) === + 'bop=boop&troll=bridge', 'incorrect string with prefix false' ); assert( - linksRe.makeParams(['bop=boop', 'troll=bridge'], '&') === '&bop=boop&troll=bridge', + linksRe.makeParams(['bop=boop', 'troll=bridge'], '&') === + '&bop=boop&troll=bridge', 'incorrect string with prefix &' ); }); it('creates the correct string when passed an object', () => { - assert(linksRe.makeParams({ bop: 'boop', troll: 'bridge' }) === '?bop=boop&troll=bridge', 'incorrect string'); assert( - linksRe.makeParams({ bop: 'boop', troll: 'bridge' }, false) === 'bop=boop&troll=bridge', + linksRe.makeParams({ bop: 'boop', troll: 'bridge' }) === + '?bop=boop&troll=bridge', + 'incorrect string' + ); + assert( + linksRe.makeParams({ bop: 'boop', troll: 'bridge' }, false) === + 'bop=boop&troll=bridge', 'incorrect string with prefix false' ); assert( - linksRe.makeParams({ bop: 'boop', troll: 'bridge' }, '&') === '&bop=boop&troll=bridge', + linksRe.makeParams({ bop: 'boop', troll: 'bridge' }, '&') === + '&bop=boop&troll=bridge', 'incorrect string with prefix &' ); }); @@ -105,37 +149,61 @@ describe('makeParams', () => { describe('determineViewMode', () => { it('returns empty string when no parameter in search', () => { - assert(linksRe.determineViewMode('') === '', linksRe.determineViewMode('') + 'not empty on empty string'); - assert(linksRe.determineViewMode('?afs=asdf') === '', 'not empty on incorrect parameter'); - assert(linksRe.determineViewMode('?afs=asdf&apple=sauce') === '', 'not empty on incorrect parameter'); + assert( + linksRe.determineViewMode('') === '', + linksRe.determineViewMode('') + 'not empty on empty string' + ); + assert( + linksRe.determineViewMode('?afs=asdf') === '', + 'not empty on incorrect parameter' + ); + assert( + linksRe.determineViewMode('?afs=asdf&apple=sauce') === '', + 'not empty on incorrect parameter' + ); }); it('returns empty string when unrecognized value for parameter in search', () => { - assert(linksRe.determineViewMode(`?${PARAM_VIEW_MODE}=asd`) === '', 'not empty on incorrect parameter value'); assert( - linksRe.determineViewMode(`?${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}1`) === '', + linksRe.determineViewMode(`?${PARAM_VIEW_MODE}=asd`) === '', + 'not empty on incorrect parameter value' + ); + assert( + linksRe.determineViewMode( + `?${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}1` + ) === '', 'not empty on incorrect parameter value' ); assert( - linksRe.determineViewMode(`?${PARAM_VIEW_MODE}=asdf&apple=sauce`) === '', + linksRe.determineViewMode( + `?${PARAM_VIEW_MODE}=asdf&apple=sauce` + ) === '', 'not empty on incorrect parameter value' ); assert( - linksRe.determineViewMode(`?apple=sauce&${PARAM_VIEW_MODE}=asdf`) === '', + linksRe.determineViewMode( + `?apple=sauce&${PARAM_VIEW_MODE}=asdf` + ) === '', 'not empty on incorrect parameter value' ); }); it('returns correct value when recognized value for parameter in search', () => { assert( - linksRe.determineViewMode(`?${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}`) === VIEW_MODE_WHISTLE, + linksRe.determineViewMode( + `?${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}` + ) === VIEW_MODE_WHISTLE, 'wrong response on correct parameter' ); assert( - linksRe.determineViewMode(`?${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}&apple=sauce`) === VIEW_MODE_WHISTLE, + linksRe.determineViewMode( + `?${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}&apple=sauce` + ) === VIEW_MODE_WHISTLE, 'wrong response on correct parameter' ); assert( - linksRe.determineViewMode(`?apple=sauce&${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}`) === VIEW_MODE_WHISTLE, + linksRe.determineViewMode( + `?apple=sauce&${PARAM_VIEW_MODE}=${VIEW_MODE_WHISTLE}` + ) === VIEW_MODE_WHISTLE, 'wrong response on correct parameter' ); }); @@ -146,7 +214,9 @@ describe('Performance', () => { const largeData = secureRandom.randomBuffer(1024 * 10).toString('hex'); it('any, ' + largeData.length + ' bytes x 10,000', () => { for (let i = 0; i < 10000; i++) { - const match = (largeData + 'https://example.com').match(linksRe.any()); + const match = (largeData + 'https://example.com').match( + linksRe.any() + ); assert(match, 'no match'); assert(match[0] === 'https://example.com', 'no match'); } @@ -161,26 +231,46 @@ describe('Performance', () => { }); it('image, ' + largeData.length + ' bytes x 10,000', () => { for (let i = 0; i < 10000; i++) { - const match = (largeData + 'https://example.com/img.jpeg').match(linksRe.image()); + const match = (largeData + 'https://example.com/img.jpeg').match( + linksRe.image() + ); assert(match, 'no match'); assert(match[0] === 'https://example.com/img.jpeg', 'no match'); } }); it('remote, ' + largeData.length + ' bytes x 10,000', () => { for (let i = 0; i < 10000; i++) { - const match = (largeData + 'https://example.com').match(linksRe.remote()); + const match = (largeData + 'https://example.com').match( + linksRe.remote() + ); assert(match, 'no match'); assert(match[0] === 'https://example.com', 'no match'); } }); it('youTube', () => { match(youtubeRegex.main, 'https://youtu.be/xG7ajrbj4zs?t=7s'); - match(youtubeRegex.main, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&t=14s'); - match(youtubeRegex.main, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&feature=youtu.be&t=14s'); + match( + youtubeRegex.main, + 'https://www.youtube.com/watch?v=xG7ajrbj4zs&t=14s' + ); + match( + youtubeRegex.main, + 'https://www.youtube.com/watch?v=xG7ajrbj4zs&feature=youtu.be&t=14s' + ); }); it('youTubeId', () => { - match(youtubeRegex.contentId, 'https://youtu.be/xG7ajrbj4zs?t=7s', 'xG7ajrbj4zs', 1); - match(youtubeRegex.contentId, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&t=14s', 'xG7ajrbj4zs', 1); + match( + youtubeRegex.contentId, + 'https://youtu.be/xG7ajrbj4zs?t=7s', + 'xG7ajrbj4zs', + 1 + ); + match( + youtubeRegex.contentId, + 'https://www.youtube.com/watch?v=xG7ajrbj4zs&t=14s', + 'xG7ajrbj4zs', + 1 + ); match( youtubeRegex.contentId, 'https://www.youtube.com/watch?v=xG7ajrbj4zs&feature=youtu.be&t=14s', @@ -189,31 +279,69 @@ describe('Performance', () => { ); }); it('threespeak', () => { - match(threespeakRegex.main, 'https://3speak.co/watch?v=artemislives/tvxkobat'); - match(threespeakRegex.main, 'https://3speak.tv/watch?v=artemislives/tvxkobat'); - match(threespeakRegex.main, 'https://3speak.co/watch?v=artemislives/tvxkobat&jwsource=cl'); - match(threespeakRegex.main, 'https://3speak.tv/watch?v=artemislives/tvxkobat&jwsource=cl'); - match(threespeakRegex.main, 'https://3speak.co/embed?v=artemislives/tvxkobat'); - match(threespeakRegex.main, 'https://3speak.tv/embed?v=artemislives/tvxkobat'); + match( + threespeakRegex.main, + 'https://3speak.co/watch?v=artemislives/tvxkobat' + ); + match( + threespeakRegex.main, + 'https://3speak.tv/watch?v=artemislives/tvxkobat' + ); + match( + threespeakRegex.main, + 'https://3speak.co/watch?v=artemislives/tvxkobat&jwsource=cl' + ); + match( + threespeakRegex.main, + 'https://3speak.tv/watch?v=artemislives/tvxkobat&jwsource=cl' + ); + match( + threespeakRegex.main, + 'https://3speak.co/embed?v=artemislives/tvxkobat' + ); + match( + threespeakRegex.main, + 'https://3speak.tv/embed?v=artemislives/tvxkobat' + ); }); it('threespeakId', () => { - match(threespeakRegex.main, 'https://3speak.co/watch?v=artemislives/tvxkobat', 'artemislives/tvxkobat', 1); + match( + threespeakRegex.main, + 'https://3speak.co/watch?v=artemislives/tvxkobat', + 'artemislives/tvxkobat', + 1 + ); match( threespeakRegex.main, 'https://3speak.co/watch?v=artemislives/tvxkobat&jwsource=cl', 'artemislives/tvxkobat', 1 ); - match(threespeakRegex.main, 'https://3speak.tv/embed?v=artemislives/tvxkobat', 'artemislives/tvxkobat', 1); + match( + threespeakRegex.main, + 'https://3speak.tv/embed?v=artemislives/tvxkobat', + 'artemislives/tvxkobat', + 1 + ); - match(threespeakRegex.main, 'https://3speak.tv/watch?v=artemislives/tvxkobat', 'artemislives/tvxkobat', 1); + match( + threespeakRegex.main, + 'https://3speak.tv/watch?v=artemislives/tvxkobat', + 'artemislives/tvxkobat', + 1 + ); match( threespeakRegex.main, 'https://3speak.tv/watch?v=artemislives/tvxkobat&jwsource=cl', 'artemislives/tvxkobat', 1 ); - match(threespeakRegex.main, 'https://3speak.tv/embed?v=artemislives/tvxkobat', 'artemislives/tvxkobat', 1); + match( + threespeakRegex.main, + 'https://3speak.tv/embed?v=artemislives/tvxkobat', + 'artemislives/tvxkobat', + 1 + ); }); it('threespeakImageLink', () => { match( @@ -222,8 +350,14 @@ describe('Performance', () => { ); }); it('twitter', () => { - match(twitterRegex.main, 'https://twitter.com/quochuync/status/1274676558641299459'); - match(twitterRegex.sanitize, 'https://twitter.com/quochuync/status/1274676558641299459?ref_src=something'); + match( + twitterRegex.main, + 'https://twitter.com/quochuync/status/1274676558641299459' + ); + match( + twitterRegex.sanitize, + 'https://twitter.com/quochuync/status/1274676558641299459?ref_src=something' + ); match( twitterRegex.htmlReplacement, '' @@ -254,12 +388,30 @@ describe('Performance', () => { ); }); it('spotify', () => { - match(spotifyRegex.main, 'https://open.spotify.com/playlist/37i9dQZF1DWSDCcNkUu5tr?si=WPhzYzqATGSIa0d3kbNgBg'); - match(spotifyRegex.main, 'https://open.spotify.com/show/37i9dQZF1DWSDCcNkUu5tr?si=WPhzYzqATGSIa0d3kbNgBg'); - match(spotifyRegex.main, 'https://open.spotify.com/episode/37i9dQZF1DWSDCcNkUu5tr?si=WPhzYzqATGSIa0d3kbNgBg'); - match(spotifyRegex.sanitize, 'https://open.spotify.com/embed/playlist/37i9dQZF1DWSDCcNkUu5tr'); - match(spotifyRegex.sanitize, 'https://open.spotify.com/embed-podcast/show/37i9dQZF1DWSDCcNkUu5tr'); - match(spotifyRegex.sanitize, 'https://open.spotify.com/embed-podcast/episode/37i9dQZF1DWSDCcNkUu5tr'); + match( + spotifyRegex.main, + 'https://open.spotify.com/playlist/37i9dQZF1DWSDCcNkUu5tr?si=WPhzYzqATGSIa0d3kbNgBg' + ); + match( + spotifyRegex.main, + 'https://open.spotify.com/show/37i9dQZF1DWSDCcNkUu5tr?si=WPhzYzqATGSIa0d3kbNgBg' + ); + match( + spotifyRegex.main, + 'https://open.spotify.com/episode/37i9dQZF1DWSDCcNkUu5tr?si=WPhzYzqATGSIa0d3kbNgBg' + ); + match( + spotifyRegex.sanitize, + 'https://open.spotify.com/embed/playlist/37i9dQZF1DWSDCcNkUu5tr' + ); + match( + spotifyRegex.sanitize, + 'https://open.spotify.com/embed-podcast/show/37i9dQZF1DWSDCcNkUu5tr' + ); + match( + spotifyRegex.sanitize, + 'https://open.spotify.com/embed-podcast/episode/37i9dQZF1DWSDCcNkUu5tr' + ); }); it('mixcloud', () => { match( @@ -274,7 +426,10 @@ describe('Performance', () => { }); it('archiveorg', () => { match(archiveorg.main, 'https://archive.org/details/geometry_dash_1.9'); - match(archiveorg.sanitize, 'https://archive.org/embed/geometry_dash_1.9'); + match( + archiveorg.sanitize, + 'https://archive.org/embed/geometry_dash_1.9' + ); }); it('bandcamp', () => { match( @@ -283,8 +438,14 @@ describe('Performance', () => { ); }); it('gist', () => { - match(gist.main, 'https://gist.github.com/huysbs/647a50197b95c4027550a2cc558af6aa'); - match(gist.sanitize, 'https://gist.github.com/huysbs/647a50197b95c4027550a2cc558af6aa.js'); + match( + gist.main, + 'https://gist.github.com/huysbs/647a50197b95c4027550a2cc558af6aa' + ); + match( + gist.sanitize, + 'https://gist.github.com/huysbs/647a50197b95c4027550a2cc558af6aa.js' + ); match( gist.htmlReplacement, '' @@ -306,21 +467,34 @@ const match = (...args) => compare(true, ...args); const matchNot = (...args) => compare(false, ...args); const compare = (matching, re, input, output = input, pos = 0) => { if (Array.isArray(input)) { - for (let i = 0; i < input.length; i++) compare(matching, re, input[i], output[i]); + for (let i = 0; i < input.length; i++) + compare(matching, re, input[i], output[i]); return; } // console.log('compare, input', input) // console.log('compare, output', output) const m = input.match(re); if (matching) { - assert(m, `No match --> ${input} --> output ${output} --> using ${re.toString()}`); + assert( + m, + `No match --> ${input} --> output ${ + output + } --> using ${re.toString()}` + ); // console.log('m', m) assert.equal( m[pos], output, - `Unmatched ${m[pos]} --> input ${input} --> output ${output} --> using ${re.toString()}` + `Unmatched ${m[pos]} --> input ${input} --> output ${ + output + } --> using ${re.toString()}` ); } else { - assert(!m, `False match --> input ${input} --> output ${output} --> using ${re.toString()}`); + assert( + !m, + `False match --> input ${input} --> output ${ + output + } --> using ${re.toString()}` + ); } }; diff --git a/src/app/utils/ParsersAndFormatters.js b/src/app/utils/ParsersAndFormatters.js index 5150e7dbf..6c15428f4 100644 --- a/src/app/utils/ParsersAndFormatters.js +++ b/src/app/utils/ParsersAndFormatters.js @@ -6,7 +6,7 @@ function fractional_part_len(value) { } // FIXME this should be unit tested.. here is one bug: 501,695,.505 -export function formatDecimal(value, decPlaces = 2, truncate0s = true) { +export function formatDecimal(value, decPlaces = 8, truncate0s = true) { let decSeparator, fl, i, j, sign, thouSeparator, abs_value; if (value === null || value === void 0 || isNaN(value)) { return ['N', 'a', 'N']; diff --git a/src/app/utils/ProxifyUrl.js b/src/app/utils/ProxifyUrl.js index 21945abf9..54c822e1e 100644 --- a/src/app/utils/ProxifyUrl.js +++ b/src/app/utils/ProxifyUrl.js @@ -14,7 +14,8 @@ const NATURAL_SIZE = '0x0/'; const CAPPED_SIZE = '768x0/'; const DOUBLE_CAPPED_SIZE = '1536x0/'; -export const imageProxy = () => $STM_Config.img_proxy_prefix; +export const imageProxy = useHive => + useHive ? $STM_Config.hive_img_proxy_prefix : $STM_Config.img_proxy_prefix; export const defaultSrcSet = (url) => { return `${url} 1x, ${url.replace(CAPPED_SIZE, DOUBLE_CAPPED_SIZE)} 2x`; }; @@ -33,14 +34,21 @@ export const defaultWidth = () => { * if true, preserves the first {int}x{int} in a proxy url. If not found, uses 0x0 * @returns string */ -export const proxifyImageUrl = (url, dimensions = false) => { +export const proxifyImageUrl = (url, useHive, dimensions = false) => { + if (!url) return; const proxyList = url.match(rProxyDomainsDimensions); let respUrl = url; if (proxyList) { const lastProxy = proxyList[proxyList.length - 1]; respUrl = url.substring(url.lastIndexOf(lastProxy) + lastProxy.length); } - if (dimensions && $STM_Config && $STM_Config.img_proxy_prefix) { + if (!$STM_Config) { + return respUrl; + } + const proxy_prefix = useHive + ? $STM_Config.hive_img_proxy_prefix + : $STM_Config.img_proxy_prefix; + if (dimensions && proxy_prefix) { let dims = dimensions + '/'; if (typeof dimensions !== 'string') { dims = proxyList @@ -58,7 +66,7 @@ export const proxifyImageUrl = (url, dimensions = false) => { (NATURAL_SIZE !== dims && CAPPED_SIZE !== dims) || !rProxyDomain.test(respUrl) ) { - return $STM_Config.img_proxy_prefix + dims + respUrl; + return proxy_prefix + dims + respUrl; } } return respUrl; diff --git a/src/app/utils/ProxifyUrl.test.js b/src/app/utils/ProxifyUrl.test.js index 48921e557..f91110476 100644 --- a/src/app/utils/ProxifyUrl.test.js +++ b/src/app/utils/ProxifyUrl.test.js @@ -144,7 +144,7 @@ describe('ProxifyUrl', () => { }); const testCase = (inputUrl, outputDims, expectedUrl) => { - const outputUrl = proxifyImageUrl(inputUrl, outputDims); + const outputUrl = proxifyImageUrl(inputUrl, false, outputDims); assert.equal( outputUrl, expectedUrl, diff --git a/src/app/utils/SanitizeConfig.js b/src/app/utils/SanitizeConfig.js index e24fce7b8..7c808cce6 100644 --- a/src/app/utils/SanitizeConfig.js +++ b/src/app/utils/SanitizeConfig.js @@ -1,4 +1,3 @@ -import { APP_DOMAIN } from 'app/client_config'; import { isDefaultImageSize, defaultSrcSet, defaultWidth } from 'app/utils/ProxifyUrl'; import { getPhishingWarningMessage, getExternalLinkWarningMessage } from 'shared/HtmlReady'; // the only allowable title attributes for div and a tags @@ -15,7 +14,7 @@ export const allowedTags = ` .split(/,\s*/); // Medium insert plugin uses: div, figure, figcaption, iframe -export default ({ large = true, highQualityPost = true, noImage = false, sanitizeErrors = [] }) => ({ +export default ({ large = true, highQualityPost = true, noImage = false, appDomain = '', sanitizeErrors = [] }) => ({ allowedTags, // figure, figcaption, @@ -142,9 +141,7 @@ export default ({ large = true, highQualityPost = true, noImage = false, sanitiz href = href.trim(); const attys = { href }; // If it's not a (relative or absolute) app URL... - if ( - !href.match(new RegExp(`^(\/(?!\/)|https:\/\/${APP_DOMAIN})`)) - ) { + if (!href.match(new RegExp(`^(\/(?!\/)|https:\/\/${appDomain})`))) { // attys.target = '_blank' // pending iframe impl https://mathiasbynens.github.io/rel-noopener/ attys.rel = highQualityPost ? 'noopener' : 'nofollow noopener'; attys.title = getExternalLinkWarningMessage(); diff --git a/src/app/utils/SlateEditor/Image.js b/src/app/utils/SlateEditor/Image.js index df535244f..81b90c5d0 100644 --- a/src/app/utils/SlateEditor/Image.js +++ b/src/app/utils/SlateEditor/Image.js @@ -75,14 +75,15 @@ export default connect( }; render() { - const { node, state, attributes } = this.props; + const { node, state, attributes, hive } = this.props; const isFocused = state.selection.hasEdgeIn(node); const className = isFocused ? 'active' : null; - const prefix = $STM_Config.img_proxy_prefix - ? $STM_Config.img_proxy_prefix + '0x0/' - : ''; + const proxy_prefix = hive + ? $STM_Config.hive_img_proxy_prefix + : $STM_Config.img_proxy_prefix; + const prefix = proxy_prefix ? proxy_prefix + '0x0/' : ''; const alt = node.data.get('alt'); const src = node.data.get('src'); diff --git a/src/app/utils/steemApi.js b/src/app/utils/steemApi.js index da52ad39a..41af74d56 100644 --- a/src/app/utils/steemApi.js +++ b/src/app/utils/steemApi.js @@ -8,17 +8,10 @@ import { augmentContentWithCrossPost, } from 'app/utils/CrossPosts'; -import { - LIQUID_TOKEN_UPPERCASE, - PREFER_HIVE, - DISABLE_HIVE, - HIVE_ENGINE, -} from 'app/client_config'; - import axios from 'axios'; import SSC from '@hive-engine/sscjs'; -const ssc = new SSC('https://api.steem-engine.net/rpc'); +const ssc = new SSC('https://ha.herpc.dtools.dev'); const hiveSsc = new SSC('https://ha.herpc.dtools.dev'); export async function callBridge(method, params, useHive = true) { @@ -59,22 +52,19 @@ async function callApi(url, params) { }); } -async function getSteemEngineAccountHistoryAsync(account, hive) { - const transfers = await callApi( - hive - ? 'https://accounts.hive-engine.com/accountHistory' - : 'https://api.steem-engine.net/history/accountHistory', +async function getSteemEngineAccountHistoryAsync(account, scotTokenSymbol, hive) { + const transfers = await callApi('https://accounts.hive-engine.com/accountHistory', { account, limit: 50, offset: 0, type: 'user', - symbol: LIQUID_TOKEN_UPPERCASE, + symbol: scotTokenSymbol, } ); const history = await getScotDataAsync('get_account_history', { account, - token: LIQUID_TOKEN_UPPERCASE, + token: scotTokenSymbol, limit: 50, }); transfers.forEach(x => (x.timestamp = x.timestamp * 1000)); @@ -85,15 +75,26 @@ async function getSteemEngineAccountHistoryAsync(account, hive) { } export async function getScotDataAsync(path, params) { - return await callApi(`https://scot-api.hive-engine.com/${path}`, params); + return await callApi(`https://ha.smt-api.dtools.dev/${path}`, params); } export async function getScotAccountDataAsync(account) { - const data = await getScotDataAsync(`@${account}`, {}); - const hiveData = DISABLE_HIVE - ? null - : await getScotDataAsync(`@${account}`, { hive: 1 }); - return { data, hiveData }; + const sscVpData = await hiveSsc.find('comments', 'votingPower', { account }); + const sscTokenData = await hiveSsc.find('tokens', 'balances', { account }); + const data = {}; + sscVpData.forEach(vpData => { + data[vpData.rewardPoolId] = { + last_vote_time: new Date(vpData.lastVoteTimestamp), + last_downvote_time: new Date(vpData.lastVoteTimestamp), + voting_power: vpData.votingPower, + downvoting_power: vpData.downvotingPower, + }; + }); + const tokenData = {}; + sscTokenData.forEach(d => { + tokenData[d.symbol] = parseFloat(d.stake) + parseFloat(d.delegationsIn); + }); + return { data, tokenData }; } async function getAccountFromNodeApi(account, useHive) { @@ -108,10 +109,10 @@ export async function getAccount(account, useHive) { return profile ? profile : {}; } -export async function getWalletAccount(account, useHive) { +export async function getWalletAccount(account, useHive, scotTokenSymbol) { const bridgeAccountObject = await getAccount(account, useHive); - const hiveEngine = HIVE_ENGINE; + const hiveEngine = useHive; const engineApi = hiveEngine ? hiveSsc : ssc; const [ tokenBalances, @@ -127,13 +128,13 @@ export async function getWalletAccount(account, useHive) { }), engineApi.find('tokens', 'pendingUnstakes', { account, - symbol: LIQUID_TOKEN_UPPERCASE, + symbol: scotTokenSymbol, }), getScotAccountDataAsync(account), - getSteemEngineAccountHistoryAsync(account, hiveEngine), + getSteemEngineAccountHistoryAsync(account, scotTokenSymbol, hiveEngine), engineApi.find('tokens', 'delegations', { $or: [{ from: account }, { to: account }], - symbol: LIQUID_TOKEN_UPPERCASE, + symbol: scotTokenSymbol, }), await getAccountFromNodeApi(account, useHive), ]); @@ -147,12 +148,9 @@ export async function getWalletAccount(account, useHive) { bridgeAccountObject.token_unstakes = tokenUnstakes; } if (tokenStatuses) { - const tokenStatusData = useHive - ? tokenStatuses.hiveData - : tokenStatuses.data; - if (tokenStatusData[LIQUID_TOKEN_UPPERCASE]) { - bridgeAccountObject.token_status = - tokenStatusData[LIQUID_TOKEN_UPPERCASE]; + const tokenStatusData = tokenStatuses.data; + if (tokenStatusData[scotTokenSymbol]) { + bridgeAccountObject.token_status = tokenStatusData[scotTokenSymbol]; bridgeAccountObject.all_token_status = tokenStatusData; } } @@ -190,7 +188,7 @@ async function getAuthorRep(feedData, useHive) { */ } -function mergeContent(content, scotData) { +function mergeContent(content, scotData, scotTokenSymbol) { const parentAuthor = content.parent_author; const parentPermlink = content.parent_permlink; const voted = content.active_votes; @@ -221,37 +219,53 @@ function mergeContent(content, scotData) { content.stats.hide = false; content.stats.gray = false; } - // Prefer parent author / permlink of content - content.parent_author = parentAuthor; - content.parent_permlink = parentPermlink; + if (typeof content.json_metadata === "string") { + content.json_metadata = JSON.parse(content.json_metadata); + } content.scotData = {}; - content.scotData[LIQUID_TOKEN_UPPERCASE] = scotData; + content.scotData[scotTokenSymbol] = scotData; +} - content.json_metadata = o2j.ifStringParseJSON(content.json_metadata); +function getCategory(d) { + let category = d.tags.split(',')[0]; + if (d.url) { + const parts = d.url.split("/"); + if (parts.length > 1) { + category = parts[1]; + } + } + return category; } -async function fetchMissingData(tag, feedType, state, feedData, useHive) { +async function fetchMissingData( + tag, + feedType, + state, + feedData, + scotTokenSymbol, + useHive +) { if (!state.content) { state.content = {}; } - const missingKeys = feedData - .filter(d => d.desc == null || d.children == null) - .map(d => d.authorperm.substr(1)) - .filter(k => !state.content[k]); - const missingContent = await Promise.all( - missingKeys.map(k => { - const authorPermlink = k.split('/'); - console.log('Unexpected missing: ' + authorPermlink); - return (useHive ? hive.api : steem.api).getContentAsync( - authorPermlink[0], - authorPermlink[1] - ); - }) - ); - missingContent.forEach(c => { - state.content[`${c.author}/${c.permlink}`] = c; - }); + //const missingKeys = feedData + // .filter(d => d.desc == null || d.children == null) + // .map(d => d.authorperm.substr(1)) + // .filter(k => !state.content[k]); + //const missingContent = await Promise.all( + // missingKeys.map(k => { + // const authorPermlink = k.split('/'); + // console.log('Unexpected missing: ' + authorPermlink); + // return (useHive ? hive.api : steem.api).getContentAsync( + // authorPermlink[0], + // authorPermlink[1] + // ); + // }) + //); + //missingContent.forEach(c => { + // state.content[`${c.author}/${c.permlink}`] = c; + //}); if (!state.discussion_idx) { state.discussion_idx = {}; @@ -264,19 +278,31 @@ async function fetchMissingData(tag, feedType, state, feedData, useHive) { if (!state.content[key]) { filteredContent[key] = { author_reputation: authorRep[d.author], - body: d.desc, - body_length: d.desc.length + 1, + body: d.body ? d.body : d.desc, + body_length: d.body ? d.body.length : d.desc.length + 1, permlink: d.authorperm.split('/')[1], - category: d.tags.split(',')[0], + category: getCategory(d), children: d.children, - replies: [], // intentional + replies: [], }; } else { filteredContent[key] = state.content[key]; } - mergeContent(filteredContent[key], d); + mergeContent(filteredContent[key], d, scotTokenSymbol); discussionIndex.push(key); }); + // second pass for replies + if (feedType === 'thread') { + feedData.forEach(d => { + const key = d.authorperm.substr(1); + if (d.parent_author && d.parent_permlink) { + const pkey = `${d.parent_author}/${d.parent_permlink}`; + if (filteredContent[pkey]) { + filteredContent[pkey].replies.push(key); + } + } + }); + } state.content = filteredContent; if (!state.discussion_idx[tag]) { state.discussion_idx[tag] = {}; @@ -291,19 +317,26 @@ async function addAccountToState(state, account, useHive) { } } -export async function attachScotData(url, state, useHive, observer, ssr = false) { +export async function attachScotData( + url, + state, + hostConfig, + useHive, + observer, + ssr = false +) { if (url === '') { url = 'trending'; } let urlParts = url.match( /^(trending|hot|created|promoted|payout|payout_comments)($|\/([^\/]+)$)/ ); - const scotTokenSymbol = LIQUID_TOKEN_UPPERCASE; + const scotTokenSymbol = hostConfig['LIQUID_TOKEN_UPPERCASE']; if (urlParts) { const feedType = urlParts[1]; const tag = urlParts[3] || ''; const discussionQuery = { - token: LIQUID_TOKEN_UPPERCASE, + token: scotTokenSymbol, limit: 20, no_votes: 1, }; @@ -319,7 +352,14 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) } // first call feed. let feedData = await getScotDataAsync(callName, discussionQuery); - await fetchMissingData(tag, feedType, state, feedData, useHive); + await fetchMissingData( + tag, + feedType, + state, + feedData, + scotTokenSymbol, + useHive + ); return; } @@ -329,7 +369,8 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) if (ssr) { state['profiles'][account] = await getWalletAccount( account, - useHive + useHive, + scotTokenSymbol ); } @@ -342,39 +383,62 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) urlParts = url.match(/^[\/]?@([^\/]+)\/feed[\/]?$/); if (urlParts) { const account = urlParts[1]; - let feedData = await getScotDataAsync('get_feed', { - token: LIQUID_TOKEN_UPPERCASE, + const feedParams = { + token: scotTokenSymbol, tag: account, limit: 20, - }); - await fetchMissingData(`@${account}`, 'feed', state, feedData, useHive); + }; + let feedData = await getScotDataAsync('get_feed', feedParams); + await fetchMissingData( + `@${account}`, + 'feed', + state, + feedData, + scotTokenSymbol, + useHive + ); return; } urlParts = url.match(/^[\/]?@([^\/]+)(\/blog)?[\/]?$/); if (urlParts) { const account = urlParts[1]; - let feedData = await getScotDataAsync('get_discussions_by_blog', { - token: LIQUID_TOKEN_UPPERCASE, + const feedParams = { + token: scotTokenSymbol, tag: account, limit: 20, include_reblogs: true, - }); + }; + let feedData = await getScotDataAsync( + 'get_discussions_by_blog', + feedParams + ); if (ssr) { await addAccountToState(state, account, useHive); } - await fetchMissingData(`@${account}`, 'blog', state, feedData, useHive); + await fetchMissingData( + `@${account}`, + 'blog', + state, + feedData, + scotTokenSymbol, + useHive + ); return; } urlParts = url.match(/^[\/]?@([^\/]+)(\/posts)?[\/]?$/); if (urlParts) { const account = urlParts[1]; - let feedData = await getScotDataAsync('get_discussions_by_blog', { - token: LIQUID_TOKEN_UPPERCASE, + const feedParams = { + token: scotTokenSymbol, tag: account, limit: 20, - }); + }; + let feedData = await getScotDataAsync( + 'get_discussions_by_blog', + feedParams + ); if (ssr) { await addAccountToState(state, account, useHive); } @@ -383,6 +447,7 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) 'posts', state, feedData, + scotTokenSymbol, useHive ); return; @@ -391,11 +456,15 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) urlParts = url.match(/^[\/]?@([^\/]+)(\/comments)?[\/]?$/); if (urlParts) { const account = urlParts[1]; - let feedData = await getScotDataAsync('get_discussions_by_comments', { - token: LIQUID_TOKEN_UPPERCASE, + const feedParams = { + token: scotTokenSymbol, tag: account, limit: 20, - }); + }; + let feedData = await getScotDataAsync( + 'get_discussions_by_comments', + feedParams + ); if (ssr) { await addAccountToState(state, account, useHive); } @@ -404,6 +473,7 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) 'comments', state, feedData, + scotTokenSymbol, useHive ); return; @@ -412,11 +482,15 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) urlParts = url.match(/^[\/]?@([^\/]+)(\/replies)?[\/]?$/); if (urlParts) { const account = urlParts[1]; - let feedData = await getScotDataAsync('get_discussions_by_replies', { - token: LIQUID_TOKEN_UPPERCASE, + const feedParams = { + token: scotTokenSymbol, tag: account, limit: 20, - }); + }; + let feedData = await getScotDataAsync( + 'get_discussions_by_replies', + feedParams + ); if (ssr) { await addAccountToState(state, account, useHive); } @@ -425,6 +499,28 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) 'replies', state, feedData, + scotTokenSymbol, + useHive + ); + return; + } + + urlParts = url.match(/^[\/]?([^\/]+)\/@([^\/]+)\/([^\/]+)$/); + if (urlParts) { + const author = urlParts[2]; + const permlink = urlParts[3]; + const threadParams = { + token: scotTokenSymbol, + author, + permlink, + }; + let threadData = await getScotDataAsync('get_thread', threadParams); + await fetchMissingData( + `@${author}/${permlink}`, + 'thread', + state, + threadData, + scotTokenSymbol, useHive ); return; @@ -432,7 +528,7 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) if (state.content) { Object.entries(state.content).forEach(entry => { - if (useHive) { + if (useHive && entry[1]) { entry[1].hive = true; } }); @@ -449,11 +545,16 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) const v = entry[1]; // Fetch SCOT data const scotData = await getScotDataAsync(`@${k}`, { - hive: useHive ? '1' : '', + token: scotTokenSymbol, + //hive: useHive ? '1' : '', }); + if (useHive && state.content[k]) { + state.content[k].hive = true; + } mergeContent( state.content[k], - scotData[LIQUID_TOKEN_UPPERCASE] + scotData[scotTokenSymbol], + scotTokenSymbol ); }) ); @@ -462,7 +563,7 @@ export async function attachScotData(url, state, useHive, observer, ssr = false) .filter( entry => (entry[1].scotData && - entry[1].scotData[LIQUID_TOKEN_UPPERCASE]) || + entry[1].scotData[scotTokenSymbol]) || (entry[1].parent_author && entry[1].parent_permlink) ) .forEach(entry => { @@ -486,21 +587,28 @@ async function getContentFromBridge(author, permlink, useHive = true) { } } -export async function getContentAsync(author, permlink) { +export async function getContentAsync( + author, + permlink, + scotTokenSymbol, + preferHive +) { let content; let scotData; - if (PREFER_HIVE) { - content = await getContentFromBridge(author, permlink, true), - content.hive = true; - scotData = await getScotDataAsync(`@${author}/${permlink}?hive=1`); + if (preferHive) { + content = await getContentFromBridge(author, permlink, true); + if (content) { + content.hive = true; + } + scotData = await getScotDataAsync(`@${author}/${permlink}`, {token: scotTokenSymbol}); } else { - content = await getContentFromBridge(author, permlink, false), - scotData = await getScotDataAsync(`@${author}/${permlink}`); + content = await getContentFromBridge(author, permlink, false); + scotData = await getScotDataAsync(`@${author}/${permlink}`, {token: scotTokenSymbol}); } if (!content) { return content; } - mergeContent(content, scotData[LIQUID_TOKEN_UPPERCASE]); + mergeContent(content, scotData[scotTokenSymbol], scotTokenSymbol.split('-')[0]); return content; } @@ -580,17 +688,19 @@ export async function getCommunityStateAsync( async function loadThread(account, permlink, useHive) { const author = account.slice(1); - const content = await callBridge( + let content = await callBridge( 'get_discussion', { author, permlink }, useHive ); - - if (content) { - // Detect fetch with scot vs fetch with getState. We use body length vs body to tell - // if it was a partial fetch. To clean up later. - const k = `${author}/${permlink}`; - content[k].body_length = content[k].body.length; + if (!content || !content[`${author}/${permlink}`]) { + content = {}; + content[`${author}/${permlink}`] = await callBridge( + 'get_post', + { author, permlink }, + useHive + ); + } else { const { content: preppedContent, keys, @@ -605,6 +715,12 @@ async function loadThread(account, permlink, useHive) { ); } } + if (content) { + // Detect fetch with scot vs fetch with getState. We use body length vs body to tell + // if it was a partial fetch. To clean up later. + const k = `${author}/${permlink}`; + content[k].body_length = content[k].body.length; + } return { content }; } @@ -716,7 +832,7 @@ function parsePath(url) { return { page, tag, sort, key }; } -export async function getStateAsync(url, observer, ssr = false) { +export async function getStateAsync(url, hostConfig, observer, ssr = false) { // strip off query string let path = url.split('?')[0]; @@ -727,7 +843,7 @@ export async function getStateAsync(url, observer, ssr = false) { path = path.substring(0, path.length - 1); // Steemit state not needed for main feeds. - const steemitApiStateNeeded = + const steemitApiStateNeeded = false;/* path !== '' && !path.match(/^(login|submit)\.html$/) && !path.match( @@ -735,7 +851,7 @@ export async function getStateAsync(url, observer, ssr = false) { ) && !path.match( /^@[^\/]+(\/(feed|blog|comments|recent-replies|transfers|posts|replies|followers|followed)?)?$/ - ); + );*/ let raw = { accounts: {}, @@ -747,7 +863,7 @@ export async function getStateAsync(url, observer, ssr = false) { let useHive = false; if (steemitApiStateNeeded) { // First get Hive state - if (DISABLE_HIVE) { + if (hostConfig['DISABLE_HIVE']) { console.log('Fetching state from Steem.'); raw = await getCommunityStateAsync(url, observer, ssr, false); } else { @@ -776,7 +892,7 @@ export async function getStateAsync(url, observer, ssr = false) { } } else { // Use Prefer HIVE setting - useHive = PREFER_HIVE; + useHive = hostConfig['PREFER_HIVE']; } if (!raw.accounts) { raw.accounts = {}; @@ -784,13 +900,14 @@ export async function getStateAsync(url, observer, ssr = false) { if (!raw.content) { raw.content = {}; } - await attachScotData(path, raw, useHive, observer, ssr); + await attachScotData(path, raw, hostConfig, useHive, observer, ssr); const cleansed = stateCleaner(raw); return cleansed; } -export async function fetchFeedDataAsync(useHive, call_name, args) { +export async function fetchFeedDataAsync(useHive, call_name, hostConfig, args) { + const scotTokenSymbol = hostConfig['LIQUID_TOKEN_UPPERCASE']; const fetchSize = args.limit; let feedData; // To indicate if there are no further pages in feed. @@ -805,7 +922,7 @@ export async function fetchFeedDataAsync(useHive, call_name, args) { let callName; let discussionQuery = { ...args, - token: LIQUID_TOKEN_UPPERCASE, + token: scotTokenSymbol, no_votes: 1, }; if (args.observer) { @@ -865,12 +982,12 @@ export async function fetchFeedDataAsync(useHive, call_name, args) { body: scotData.desc, body_length: scotData.desc.length + 1, permlink: scotData.authorperm.split('/')[1], - category: scotData.tags.split(',')[0], + category: getCategory(scotData), children: scotData.children, replies: [], // intentional }; } - mergeContent(content, scotData); + mergeContent(content, scotData, scotTokenSymbol); return content; }) ); @@ -883,22 +1000,6 @@ export async function fetchFeedDataAsync(useHive, call_name, args) { // this indicates no further pages in feed. endOfData = feedData.length < fetchSize; lastValue = feedData.length > 0 ? feedData[feedData.length - 1] : null; - } else { - feedData = await (useHive ? hive.api : steem.api)[call_name](args); - feedData = await Promise.all( - feedData.map(async post => { - const k = `${post.author}/${post.permlink}`; - const scotData = await getScotDataAsync(`@${k}`); - mergeContent(post, scotData[LIQUID_TOKEN_UPPERCASE]); - return post; - }) - ); - // endOfData check and lastValue setting should go before any filtering, - endOfData = feedData.length < fetchSize; - lastValue = feedData.length > 0 ? feedData[feedData.length - 1] : null; - feedData = feedData.filter( - post => post.scotData && post.scotData[LIQUID_TOKEN_UPPERCASE] - ); } return { feedData, endOfData, lastValue }; } diff --git a/src/server/api/general.js b/src/server/api/general.js index 7e8938a03..d3bb85417 100644 --- a/src/server/api/general.js +++ b/src/server/api/general.js @@ -187,35 +187,35 @@ export default function useGeneralApi(app) { } }); - router.post('/csp_violation', function*() { - if (rateLimitReq(this, this.req)) return; - let params; - try { - params = yield coBody(this); - } catch (error) { - console.log('-- /csp_violation error -->', error); - } - if (params && params['csp-report']) { - const csp_report = params['csp-report']; - const value = `${csp_report['document-uri']} : ${ - csp_report['blocked-uri'] - }`; - console.log( - '-- /csp_violation -->', - value, - '--', - this.req.headers['user-agent'] - ); - } else { - console.log( - '-- /csp_violation [no csp-report] -->', - params, - '--', - this.req.headers['user-agent'] - ); - } - this.body = ''; - }); + // router.post('/csp_violation', function*() { + // if (rateLimitReq(this, this.req)) return; + // let params; + // try { + // params = yield coBody(this); + // } catch (error) { + // console.log('-- /csp_violation error -->', error); + // } + // if (params && params['csp-report']) { + // const csp_report = params['csp-report']; + // const value = `${csp_report['document-uri']} : ${ + // csp_report['blocked-uri'] + // }`; + // console.log( + // '-- /csp_violation -->', + // value, + // '--', + // this.req.headers['user-agent'] + // ); + // } else { + // console.log( + // '-- /csp_violation [no csp-report] -->', + // params, + // '--', + // this.req.headers['user-agent'] + // ); + // } + // this.body = ''; + // }); router.post('/setUserPreferences', koaBody, function*() { const params = this.request.body; diff --git a/src/server/app_render.jsx b/src/server/app_render.jsx index 2e900b7a7..02b175b78 100644 --- a/src/server/app_render.jsx +++ b/src/server/app_render.jsx @@ -8,6 +8,7 @@ import secureRandom from 'secure-random'; import ErrorPage from 'server/server-error'; import { determineViewMode } from '../app/utils/Links'; import { getSupportedLocales } from './utils/misc'; +import { CONFIG_MAP } from 'app/client_config'; const path = require('path'); const ROOT = path.join(__dirname, '../..'); @@ -72,13 +73,26 @@ async function appRender(ctx, locales = false, resolvedAssets = false) { api_key: config.cookie_consent_api_key, }; // ... and that's the end of user-session-related SSR + const host = ctx.request.headers.host; + console.log(`Using config for host ${host}`); const initial_state = { app: { + host, + hostConfig: CONFIG_MAP[host], viewMode: determineViewMode(ctx.request.search), googleAds: googleAds, env: process.env.NODE_ENV, walletUrl: config.wallet_url, - scotConfig: ctx.scotConfigData, + scotConfig: { + info: + ctx.scotConfigData.info[ + CONFIG_MAP[host]['LIQUID_TOKEN_UPPERCASE'] + ], + config: + ctx.scotConfigData.config[ + CONFIG_MAP[host]['LIQUID_TOKEN_UPPERCASE'] + ], + }, reviveEnabled: config.revive_enabled, }, }; @@ -130,6 +144,7 @@ async function appRender(ctx, locales = false, resolvedAssets = false) { reviveEnabled: config.revive_enabled, shouldSeeCookieConsent: cookieConsent.enabled, cookieConsentApiKey: cookieConsent.api_key, + hostConfig: CONFIG_MAP[host], }; ctx.status = statusCode; ctx.body = diff --git a/src/server/index.js b/src/server/index.js index 0def3e519..eb8eb4754 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -31,9 +31,11 @@ global.$STM_Config = { chain_id: config.get('chain_id'), address_prefix: config.get('address_prefix'), img_proxy_prefix: config.get('img_proxy_prefix'), + hive_img_proxy_prefix: config.get('hive_img_proxy_prefix'), ipfs_prefix: config.get('ipfs_prefix'), read_only_mode: config.get('read_only_mode'), upload_image: config.get('upload_image'), + hive_upload_image: config.get('hive_upload_image'), site_domain: config.get('site_domain'), google_analytics_id: config.get('google_analytics_id'), gtag_measurement_id: config.get('gtag_measurement_id'), diff --git a/src/server/server-error.jsx b/src/server/server-error.jsx index c25c72446..0948b6a1d 100644 --- a/src/server/server-error.jsx +++ b/src/server/server-error.jsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import { APP_NAME } from 'app/client_config'; class ServerError extends Component { render() { @@ -21,7 +20,7 @@ class ServerError extends Component {

    Sorry.

    Looks like something went wrong on our end.

    - Head back to {APP_NAME} homepage. + Head back to homepage.

    diff --git a/src/server/server-html.jsx b/src/server/server-html.jsx index 671d95445..000eb8842 100644 --- a/src/server/server-html.jsx +++ b/src/server/server-html.jsx @@ -1,6 +1,5 @@ import * as config from 'config'; import React from 'react'; -import { APP_NAME, DISCORD_SERVER, DISCORD_CHANNEL } from 'app/client_config'; export default function ServerHTML({ body, @@ -17,8 +16,10 @@ export default function ServerHTML({ reviveEnabled, shouldSeeCookieConsent, cookieConsentApiKey, + hostConfig, }) { let page_title = title; + const faviconSubfolder = hostConfig['LIQUID_TOKEN_UPPERCASE'].toLowerCase(); return ( @@ -36,47 +37,66 @@ export default function ServerHTML({ return ; return null; })} - + ) : null} - {adClient ? ( + {hostConfig['GOOGLE_AD_CLIENT'] ? (