From b3a4c7aa436470932746acab676ca7ba63da5022 Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 2 Aug 2020 10:56:36 -0500 Subject: [PATCH] posts: rewrite tag counter widget in React. Rewrite the tag counter widget (the one above the tag edit box) to use React. This is a trial run for using React elsewhere. We actually use Preact instead of React because it's lighter weight. We use Mobx for state management because it's cleaner than React's setState or useState. This incidentally fixes a couple bugs: * The tag counter wasn't updated when deleting tags with backspace * The tag counter wasn't updated when pasting in new tags. --- app/javascript/packs/application.js | 1 + app/javascript/src/javascripts/posts.js.erb | 29 ----- app/javascript/src/javascripts/tag_counter.js | 49 +++++++++ app/views/posts/partials/show/_edit.html.erb | 3 +- app/views/uploads/new.html.erb | 3 +- babel.config.js | 7 ++ config/webpack/environment.js | 4 +- package.json | 5 + yarn.lock | 102 +++++++++++++++++- 9 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 app/javascript/src/javascripts/tag_counter.js diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 405431059..3d7236286 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -43,6 +43,7 @@ export { default as PostTooltip } from '../src/javascripts/post_tooltips.js'; export { default as PostVersion } from '../src/javascripts/post_version.js'; export { default as RelatedTag } from '../src/javascripts/related_tag.js'; export { default as Shortcuts } from '../src/javascripts/shortcuts.js'; +export { default as TagCounter } from '../src/javascripts/tag_counter.js'; export { default as Upload } from '../src/javascripts/uploads.js.erb'; export { default as UserTooltip } from '../src/javascripts/user_tooltips.js'; export { default as Utility } from '../src/javascripts/utility.js'; diff --git a/app/javascript/src/javascripts/posts.js.erb b/app/javascript/src/javascripts/posts.js.erb index 3ed6c68ed..c85b9c6dc 100644 --- a/app/javascript/src/javascripts/posts.js.erb +++ b/app/javascript/src/javascripts/posts.js.erb @@ -42,8 +42,6 @@ Post.initialize_all = function() { } var $fields_multiple = $('[data-autocomplete="tag-edit"]'); - $fields_multiple.on("keypress.danbooru", Post.update_tag_count); - $fields_multiple.on("click", Post.update_tag_count); $(window).on('danbooru:initialize_saved_seraches', () => { Post.initialize_saved_searches(); @@ -402,7 +400,6 @@ Post.initialize_post_sections = function() { $("#post_tag_string").focus().selectEnd().height($("#post_tag_string")[0].scrollHeight); $("#recommended").hide(); $(document).trigger("danbooru:open-post-edit-tab"); - Post.update_tag_count({target: $("#post_tag_string")}); } else if (e.target.hash === "#recommended") { $("#comments").hide(); $("#edit").hide(); @@ -513,32 +510,6 @@ Post.initialize_recommended = function() { }); }; -Post.update_tag_count = function(event) { - let string = "0 tags"; - let count = 0; - - if (event) { - let tags = Utility.regexp_split($(event.target).val()); - if (tags.length) { - count = tags.length; - string = (count === 1) ? (count + " tag") : (count + " tags") - } - } - - $("#tags-container .count").html(string); - let klass = ""; - - if (count < Post.LOW_TAG_COUNT) { - klass = "frown"; - } else if (count >= Post.LOW_TAG_COUNT && count < Post.HIGH_TAG_COUNT) { - klass = "meh"; - } else { - klass = "smile"; - } - - $("#tags-container .options #face").removeClass().addClass(`far fa-${klass}`); -} - $(document).ready(function() { Post.initialize_all(); }); diff --git a/app/javascript/src/javascripts/tag_counter.js b/app/javascript/src/javascripts/tag_counter.js new file mode 100644 index 000000000..3aa537cf7 --- /dev/null +++ b/app/javascript/src/javascripts/tag_counter.js @@ -0,0 +1,49 @@ +import { h, Component, render } from "preact"; +import { observable, computed, action } from "mobx"; +import { observer } from "mobx-react"; + +import Utility from "./utility"; + +export default @observer class TagCounter extends Component { + static lowCount = 10; + static highCount = 20; + + @observable tagCount = 0; + + componentDidMount() { + $(this.props.tags).on("input", this.updateCount); + this.updateCount(); + } + + render() { + return ( + + {this.tagCount} / {TagCounter.highCount} tags + + + ); + } + + @action.bound updateCount() { + this.tagCount = Utility.regexp_split($(this.props.tags).val()).length; + } + + @computed get emoji() { + if (this.tagCount < TagCounter.lowCount) { + return "frown"; + } else if (this.tagCount >= TagCounter.lowCount && this.tagCount < TagCounter.highCount) { + return "meh"; + } else { + return "smile"; + } + } + + static initialize() { + $("[data-tag-counter]").toArray().forEach(element => { + let target = $($(element).attr("data-for")).get(0); + render(h(TagCounter, { tags: target }), element); + }); + } +} + +$(TagCounter.initialize); diff --git a/app/views/posts/partials/show/_edit.html.erb b/app/views/posts/partials/show/_edit.html.erb index 93a07e9d6..6df702965 100644 --- a/app/views/posts/partials/show/_edit.html.erb +++ b/app/views/posts/partials/show/_edit.html.erb @@ -50,8 +50,7 @@ <%= f.label :tag_string, "Tags" %> - - + diff --git a/app/views/uploads/new.html.erb b/app/views/uploads/new.html.erb index c34bdaf92..8bc5b0fc1 100644 --- a/app/views/uploads/new.html.erb +++ b/app/views/uploads/new.html.erb @@ -66,8 +66,7 @@ <%= f.label :tag_string, "Tags" %> - - + diff --git a/babel.config.js b/babel.config.js index 12f98da5a..2f5c9da10 100644 --- a/babel.config.js +++ b/babel.config.js @@ -41,6 +41,13 @@ module.exports = function(api) { '@babel/plugin-syntax-dynamic-import', isTestEnv && 'babel-plugin-dynamic-import-node', '@babel/plugin-transform-destructuring', + ["@babel/plugin-proposal-decorators", { + legacy: true + }], + ["@babel/plugin-transform-react-jsx", { + pragma: "h", + pragmaFrag: "Fragment", + }], [ '@babel/plugin-proposal-class-properties', { diff --git a/config/webpack/environment.js b/config/webpack/environment.js index 607382c5e..1cb4e0d86 100644 --- a/config/webpack/environment.js +++ b/config/webpack/environment.js @@ -7,7 +7,9 @@ environment.loaders.append('erb', erb); environment.config.output.library = ["Danbooru"]; environment.config.set("resolve.alias", { - "jquery": "jquery/src/jquery.js" + "jquery": "jquery/src/jquery.js", + "react": "preact/compat", + "react-dom": "preact/compat", }); module.exports = environment diff --git a/package.json b/package.json index 66940300e..c396790fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "license": "MIT", "dependencies": { + "@babel/plugin-proposal-decorators": "^7.10.5", + "@babel/plugin-transform-react-jsx": "^7.10.4", "@fortawesome/fontawesome-free": "^5.11.2", "@rails/ujs": "^6.0.2-1", "@rails/webpacker": "^5.0.0", @@ -9,6 +11,9 @@ "jquery": "3.5.1", "jquery-hotkeys": "^0.2.2", "jquery-ui": "^1.12.1", + "mobx": "^5.15.4", + "mobx-react": "^6.2.5", + "preact": "^10.4.6", "rails-erb-loader": "^5.5.0", "spark-md5": "^3.0.0", "tippy.js": "^6.2.3", diff --git a/yarn.lock b/yarn.lock index 3a219010c..925a17179 100644 --- a/yarn.lock +++ b/yarn.lock @@ -65,6 +65,23 @@ "@babel/helper-explode-assignable-expression" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/helper-builder-react-jsx-experimental@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.5.tgz#f35e956a19955ff08c1258e44a515a6d6248646b" + integrity sha512-Buewnx6M4ttG+NLkKyt7baQn7ScC/Td+e99G914fRU8fGIUivDDgVIQeDHFa5e4CRSJQt58WpNHhsAZgtzVhsg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/types" "^7.10.5" + +"@babel/helper-builder-react-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz#8095cddbff858e6fa9c326daee54a2f2732c1d5d" + integrity sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-compilation-targets@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" @@ -88,6 +105,18 @@ "@babel/helper-replace-supers" "^7.10.4" "@babel/helper-split-export-declaration" "^7.10.4" +"@babel/helper-create-class-features-plugin@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" + integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.10.5" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/helper-create-regexp-features-plugin@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" @@ -144,6 +173,13 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-member-expression-to-functions@^7.10.5": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" + integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== + dependencies: + "@babel/types" "^7.11.0" + "@babel/helper-module-imports@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" @@ -274,6 +310,15 @@ "@babel/helper-create-class-features-plugin" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-proposal-decorators@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.5.tgz#42898bba478bc4b1ae242a703a953a7ad350ffb4" + integrity sha512-Sc5TAQSZuLzgY0664mMDn24Vw2P8g/VhyLyGPaWiHahhgLqeZvcGeyBZOrJW0oSKIK2mvQ22a1ENXBIQLhrEiQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-decorators" "^7.10.4" + "@babel/plugin-proposal-dynamic-import@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" @@ -361,6 +406,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-decorators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz#6853085b2c429f9d322d02f5a635018cdeb2360c" + integrity sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -375,6 +427,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c" + integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" @@ -602,6 +661,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-react-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz#673c9f913948764a4421683b2bef2936968fddf2" + integrity sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A== + dependencies: + "@babel/helper-builder-react-jsx" "^7.10.4" + "@babel/helper-builder-react-jsx-experimental" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" + "@babel/plugin-transform-regenerator@^7.10.4", "@babel/plugin-transform-regenerator@^7.8.7": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" @@ -799,6 +868,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.10.5", "@babel/types@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" + integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@csstools/convert-colors@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" @@ -4969,7 +5047,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.5, lodash@~4.17.10: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== @@ -5374,6 +5452,23 @@ mixin-deep@^1.2.0: dependencies: minimist "^1.2.5" +mobx-react-lite@>=2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-2.0.7.tgz#1bfb3b4272668e288047cf0c7940b14e91cba284" + integrity sha512-YKAh2gThC6WooPnVZCoC+rV1bODAKFwkhxikzgH18wpBjkgTkkR9Sb0IesQAH5QrAEH/JQVmy47jcpQkf2Au3Q== + +mobx-react@^6.2.5: + version "6.2.5" + resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-6.2.5.tgz#9020a17b79cc6dc3d124ad89ab36eb9ea540a45b" + integrity sha512-LxtXXW0GkOAO6VOIg2m/6WL6ZuKlzOWwESIFdrWelI0ZMIvtKCMZVUuulcO5GAWSDsH0ApaMkGLoaPqKjzyziQ== + dependencies: + mobx-react-lite ">=2.0.6" + +mobx@^5.15.4: + version "5.15.4" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.15.4.tgz#9da1a84e97ba624622f4e55a0bf3300fb931c2ab" + integrity sha512-xRFJxSU2Im3nrGCdjSuOTFmxVDGeqOHL+TyADCGbT0k4HHqGmx5u2yaHNryvoORpI4DfbzjJ5jPmuv+d7sioFw== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -6851,6 +6946,11 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.1 source-map "^0.6.1" supports-color "^6.1.0" +preact@^10.4.6: + version "10.4.6" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.4.6.tgz#86cc43396e4bdd755726a2b4b1f0529e78067cd3" + integrity sha512-80WJfXH53yyINig5Wza/8MD9n4lMg9G6aN00ws0ptsAaY/Fu/M7xW4zICf7OLfocVltxS30wvNQ8oIbUyZS1tw== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"