From 74335a6d45c59c93806e9ae75d172e3aad7a2ba0 Mon Sep 17 00:00:00 2001 From: Nathan Walters Date: Tue, 9 Oct 2018 23:46:41 -0500 Subject: [PATCH] Add custom message to queue page (#148) * Add message to queue schema * Finish queue message support * Render message with Markdown * Add changelog entry * Run prettier --- CHANGELOG.md | 1 + package-lock.json | 310 +++++++++++++++++- package.json | 1 + src/api/queues.js | 7 + src/components/QueueMessage.js | 101 ++++++ src/components/QueueMessageEnabledToggle.js | 44 +++ src/components/QueueStatusToggle.js | 7 +- src/containers/QueueMessageContainer.js | 26 ++ .../QueueMessageEnabledToggleContainer.js | 14 + .../20181009234426-queue-message.js | 22 ++ src/models/Queue.js | 5 + src/pages/queue.js | 8 + 12 files changed, 532 insertions(+), 14 deletions(-) create mode 100644 src/components/QueueMessage.js create mode 100644 src/components/QueueMessageEnabledToggle.js create mode 100644 src/containers/QueueMessageContainer.js create mode 100644 src/containers/QueueMessageEnabledToggleContainer.js create mode 100644 src/migrations/20181009234426-queue-message.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f1213685..682a8756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ with the current date and the next changes should go under a **[Next]** header. * Upgrade to Next 7 and React 16.5.2. ([@nwalters512](https://github.com/nwalters512) in [#143](https://github.com/illinois/queue/pull/143)) * Sort queues and courses by name. ([@genevievehelsel](https://github.com/genevievehelsel) in [#144](https://github.com/illinois/queue/pull/144)) * Fix bug on course page with queues shown. ([@genevievehelsel](https://github.com/genevievehelsel) in [#145](https://github.com/illinois/queue/pull/145)) +* Add ability to write custom messages on queues. ([@nwalters512](https://github.com/nwalters512) in [#148](https://github.com/illinois/queue/pull/148)) ## 5 September 2018 diff --git a/package-lock.json b/package-lock.json index 53bb7b9f..8a0a3a6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2057,6 +2057,11 @@ "commander": "^2.11.0" } }, + "arity-n": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", + "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=" + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -3371,6 +3376,11 @@ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, + "bail": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", + "integrity": "sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==" + }, "balanced-match": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", @@ -3945,6 +3955,21 @@ "supports-color": "^2.0.0" } }, + "character-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", + "integrity": "sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==" + }, + "character-entities-legacy": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz", + "integrity": "sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==" + }, + "character-reference-invalid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz", + "integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==" + }, "chardet": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", @@ -3976,6 +4001,11 @@ } } }, + "chickencurry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chickencurry/-/chickencurry-1.1.1.tgz", + "integrity": "sha1-AmVfKyazvC7hrh5TFoht4463lzg=" + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -4228,6 +4258,11 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, + "collapse-white-space": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.4.tgz", + "integrity": "sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==" + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -4357,6 +4392,14 @@ "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, + "compose-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-2.0.0.tgz", + "integrity": "sha1-5kL6fh2iFSlyADFHZ3b8JGkawLA=", + "requires": { + "arity-n": "^1.0.4" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5118,7 +5161,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, "requires": { "domelementtype": "~1.1.1", "entities": "~1.1.1" @@ -5127,8 +5169,7 @@ "domelementtype": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } }, @@ -5140,8 +5181,7 @@ "domelementtype": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" }, "domexception": { "version": "1.0.1", @@ -5156,7 +5196,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", - "dev": true, "requires": { "domelementtype": "1" } @@ -5165,7 +5204,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, "requires": { "dom-serializer": "0", "domelementtype": "1" @@ -5389,8 +5427,7 @@ "entities": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", - "dev": true + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" }, "env-variable": { "version": "0.0.4", @@ -6479,8 +6516,7 @@ "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extend-shallow": { "version": "3.0.2", @@ -7852,6 +7888,18 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" }, + "html-to-react": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.3.3.tgz", + "integrity": "sha512-4Qi5/t8oBr6c1t1kBJKyxEeJu0lb7ctvq29oFZioiUHH0Wz88VWGwoXuH26HDt9v64bDHA4NMPNTH8bVrcaJWA==", + "requires": { + "domhandler": "^2.3.0", + "escape-string-regexp": "^1.0.5", + "htmlparser2": "^3.8.3", + "ramda": "^0.25.0", + "underscore.string.fp": "^1.0.4" + } + }, "htmlescape": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", @@ -7861,7 +7909,6 @@ "version": "3.9.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", - "dev": true, "requires": { "domelementtype": "^1.3.0", "domhandler": "^2.3.0", @@ -8305,6 +8352,20 @@ "kind-of": "^6.0.0" } }, + "is-alphabetical": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.2.tgz", + "integrity": "sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg==" + }, + "is-alphanumerical": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz", + "integrity": "sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -8368,6 +8429,11 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, + "is-decimal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.2.tgz", + "integrity": "sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==" + }, "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", @@ -8438,6 +8504,11 @@ "is-extglob": "^1.0.0" } }, + "is-hexadecimal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz", + "integrity": "sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==" + }, "is-installed-globally": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", @@ -8619,11 +8690,21 @@ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, + "is-whitespace-character": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz", + "integrity": "sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ==" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=" }, + "is-word-character": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.2.tgz", + "integrity": "sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -11351,6 +11432,11 @@ "object-visit": "^1.0.0" } }, + "markdown-escapes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.2.tgz", + "integrity": "sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA==" + }, "math-expression-evaluator": { "version": "1.2.17", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", @@ -11382,6 +11468,14 @@ "inherits": "^2.0.1" } }, + "mdast-add-list-metadata": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz", + "integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==", + "requires": { + "unist-util-visit-parents": "1.1.2" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -12666,6 +12760,19 @@ "pbkdf2": "^3.0.3" } }, + "parse-entities": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.0.tgz", + "integrity": "sha512-XXtDdOPLSB0sHecbEapQi6/58U/ODj/KWfIXmmMCJF/eRn8laX6LZbOyioMoETOOJoWRW8/qTSl5VQkUIfKM5g==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -13872,6 +13979,11 @@ "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", "dev": true }, + "ramda": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" + }, "randexp": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", @@ -14000,6 +14112,20 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-markdown": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.0.2.tgz", + "integrity": "sha512-kgMJysfvqj8o9sQejCrQCoMt4R6S+7lLngOkbgiuRo3Sh4HtjSEHcUCFChNK2f8RRhn0egfdNm5usQF+DXHmEg==", + "requires": { + "html-to-react": "^1.3.3", + "mdast-add-list-metadata": "1.0.1", + "prop-types": "^15.6.1", + "remark-parse": "^5.0.0", + "unified": "^6.1.5", + "unist-util-visit": "^1.3.0", + "xtend": "^4.0.1" + } + }, "react-moment": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/react-moment/-/react-moment-0.7.9.tgz", @@ -14375,6 +14501,28 @@ } } }, + "remark-parse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", + "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "requires": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -14399,6 +14547,11 @@ "is-finite": "^1.0.0" } }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + }, "request": { "version": "2.87.0", "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", @@ -14538,6 +14691,11 @@ "debug": "^2.6.9" } }, + "reverse-arguments": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reverse-arguments/-/reverse-arguments-1.0.0.tgz", + "integrity": "sha1-woCVo6khrHFdYYNN3s6QJ5kmZ80=" + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -15334,6 +15492,11 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.4.tgz", "integrity": "sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==" }, + "state-toggle": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.1.tgz", + "integrity": "sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og==" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -16158,16 +16321,31 @@ } } }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, + "trim-trailing-lines": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz", + "integrity": "sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg==" + }, "triple-beam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "trough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.3.tgz", + "integrity": "sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw==" + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -16435,11 +16613,36 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" }, + "underscore.string": { + "version": "3.0.3", + "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-3.0.3.tgz", + "integrity": "sha1-Rhe4waJQz25QZPu7Nj0PqWzxRVI=" + }, + "underscore.string.fp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/underscore.string.fp/-/underscore.string.fp-1.0.4.tgz", + "integrity": "sha1-BUs/GEO8rlYShsh95eiHm0/Jg2Q=", + "requires": { + "chickencurry": "1.1.1", + "compose-function": "^2.0.0", + "reverse-arguments": "1.0.0", + "underscore.string": "3.0.3" + } + }, "unfetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-3.0.0.tgz", "integrity": "sha1-jR4FE6Ts0OX/LUGmund3Gq6LZII=" }, + "unherit": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.1.tgz", + "integrity": "sha512-+XZuV691Cn4zHsK0vkKYwBEwB74T3IZIcxrgn2E4rKwTfFyI1zCh7X7grwh9Re08fdPlarIdyWgI8aVB3F5A5g==", + "requires": { + "inherits": "^2.0.1", + "xtend": "^4.0.1" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -16464,6 +16667,19 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==" }, + "unified": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", + "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^2.0.0", + "x-is-string": "^0.1.0" + } + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -16531,6 +16747,47 @@ "crypto-random-string": "^1.0.0" } }, + "unist-util-is": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.2.tgz", + "integrity": "sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==" + }, + "unist-util-remove-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz", + "integrity": "sha512-XxoNOBvq1WXRKXxgnSYbtCF76TJrRoe5++pD4cCBsssSiWSnPEktyFrFLE8LTk3JW5mt9hB0Sk5zn4x/JeWY7Q==", + "requires": { + "unist-util-visit": "^1.1.0" + } + }, + "unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==" + }, + "unist-util-visit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.0.tgz", + "integrity": "sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==", + "requires": { + "unist-util-visit-parents": "^2.0.0" + }, + "dependencies": { + "unist-util-visit-parents": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz", + "integrity": "sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA==", + "requires": { + "unist-util-is": "^2.1.2" + } + } + } + }, + "unist-util-visit-parents": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz", + "integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -16763,6 +17020,30 @@ "extsprintf": "^1.2.0" } }, + "vfile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", + "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "requires": { + "is-buffer": "^1.1.4", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + }, + "vfile-location": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.3.tgz", + "integrity": "sha512-zM5/l4lfw1CBoPx3Jimxoc5RNDAHHpk6AM6LM0pTIkm5SUSsx8ZekZ0PVdf0WEZ7kjlhSt7ZlqbRL6Cd6dBs6A==" + }, + "vfile-message": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.0.1.tgz", + "integrity": "sha512-vSGCkhNvJzO6VcWC6AlJW4NtYOVtS+RgCaqFIYUjoGIlHnFL+i0LbtYvonDWOMcB97uTPT4PRsyYY7REWC9vug==", + "requires": { + "unist-util-stringify-position": "^1.1.1" + } + }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -17429,6 +17710,11 @@ "safe-buffer": "~5.1.0" } }, + "x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=" + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", diff --git a/package.json b/package.json index 3e3be498..8e499b91 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "react-dom": "^16.5.2", "react-flip-move": "^3.0.2", "react-fontawesome": "^1.6.1", + "react-markdown": "^4.0.2", "react-moment": "^0.7.9", "react-redux": "^5.0.7", "react-toggle": "^4.0.2", diff --git a/src/api/queues.js b/src/api/queues.js index 2ac1f64e..693311b7 100644 --- a/src/api/queues.js +++ b/src/api/queues.js @@ -121,6 +121,10 @@ router.patch( check('open') .optional({ nullable: true }) .isBoolean(), + check('message').optional({ nullable: true }), + check('messageEnabled') + .optional({ nullable: true }) + .isBoolean(), validateLocation, failIfErrors, ], @@ -132,6 +136,9 @@ router.patch( name: data.name !== null ? data.name : undefined, location: data.location !== null ? data.location : undefined, open: data.open !== null ? data.open : undefined, + message: data.message !== null ? data.message : undefined, + messageEnabled: + data.messageEnabled !== null ? data.messageEnabled : undefined, }) const updatedQueue = await Queue.scope('questionCount').findOne({ diff --git a/src/components/QueueMessage.js b/src/components/QueueMessage.js new file mode 100644 index 00000000..31211087 --- /dev/null +++ b/src/components/QueueMessage.js @@ -0,0 +1,101 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import { Alert, Button, FormText, Input } from 'reactstrap' +import ReactMarkdown from 'react-markdown' + +class QueueMessage extends React.Component { + constructor(props) { + super(props) + + this.state = { + editing: false, + editedMessage: null, + } + + this.onMessageChanged = this.onMessageChanged.bind(this) + this.onStartEdit = this.onStartEdit.bind(this) + this.onFinishEdit = this.onFinishEdit.bind(this) + } + + onMessageChanged(e) { + this.setState({ + editedMessage: e.target.value, + }) + } + + onStartEdit() { + this.setState({ + editing: true, + editedMessage: this.props.message, + }) + } + + onFinishEdit() { + const attributes = { + message: this.state.editedMessage, + } + this.props.updateQueue(this.props.queueId, attributes).then(() => { + this.setState({ editing: false }) + }) + } + + render() { + const { message, isUserCourseStaff } = this.props + const { editing, editedMessage } = this.state + + // If the user is not on course staff and the message is null or empty, + // don't render the panel to them. + if (!isUserCourseStaff && !message) { + return null + } + + let content + let button + if (editing) { + content = ( + <> + + + You can use Markdown to format this message. + + + ) + button = ( + + ) + } else { + content = + button = ( + + ) + } + + return ( + +
A message from the queue staff
+ {content} + {isUserCourseStaff &&
{button}
} +
+ ) + } +} + +QueueMessage.propTypes = { + queueId: PropTypes.number.isRequired, + isUserCourseStaff: PropTypes.bool.isRequired, + message: PropTypes.string.isRequired, + updateQueue: PropTypes.func.isRequired, +} + +export default QueueMessage diff --git a/src/components/QueueMessageEnabledToggle.js b/src/components/QueueMessageEnabledToggle.js new file mode 100644 index 00000000..f3f75760 --- /dev/null +++ b/src/components/QueueMessageEnabledToggle.js @@ -0,0 +1,44 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Toggle from 'react-toggle' + +class QueueMessageEnabledToggle extends React.Component { + constructor(props) { + super(props) + this.handleCheckChanged = this.handleCheckChanged.bind(this) + } + + handleCheckChanged(e) { + const attributes = { + messageEnabled: e.target.checked, + } + this.props.updateQueue(this.props.queue.id, attributes) + } + + render() { + return ( +
+ Show staff message + +
+ ) + } +} + +QueueMessageEnabledToggle.defaultProps = { + queue: null, +} + +QueueMessageEnabledToggle.propTypes = { + queue: PropTypes.shape({ + id: PropTypes.number, + messageEnabled: PropTypes.bool, + }), + updateQueue: PropTypes.func.isRequired, +} + +export default QueueMessageEnabledToggle diff --git a/src/components/QueueStatusToggle.js b/src/components/QueueStatusToggle.js index e77ade2e..c017e120 100644 --- a/src/components/QueueStatusToggle.js +++ b/src/components/QueueStatusToggle.js @@ -1,11 +1,14 @@ -/* eslint-env browser */ import React from 'react' import PropTypes from 'prop-types' import { Button } from 'reactstrap' +import 'react-toggle/style.css' class QueueStatusToggle extends React.Component { toggleQueueStatus() { - const attributes = { open: !this.props.queue.open } + const attributes = { + open: !this.props.queue.open, + message: this.props.queue.open ? 'Closed' : 'Open', + } this.props.updateQueue(this.props.queue.id, attributes) } diff --git a/src/containers/QueueMessageContainer.js b/src/containers/QueueMessageContainer.js new file mode 100644 index 00000000..f7118a66 --- /dev/null +++ b/src/containers/QueueMessageContainer.js @@ -0,0 +1,26 @@ +import { connect } from 'react-redux' + +import { updateQueue } from '../actions/queue' +import { isUserCourseStaffForQueue, isUserAdmin } from '../selectors' + +import QueueMessage from '../components/QueueMessage' + +const mapStateToProps = (state, ownProps) => { + const queue = state.queues.queues[ownProps.queueId] + return { + message: queue ? queue.message : null, + isUserCourseStaff: + isUserCourseStaffForQueue(state, ownProps) || + isUserAdmin(state, ownProps), + } +} + +const mapDispatchToProps = dispatch => ({ + updateQueue: (queueId, attributes) => + dispatch(updateQueue(queueId, attributes)), +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(QueueMessage) diff --git a/src/containers/QueueMessageEnabledToggleContainer.js b/src/containers/QueueMessageEnabledToggleContainer.js new file mode 100644 index 00000000..68c63373 --- /dev/null +++ b/src/containers/QueueMessageEnabledToggleContainer.js @@ -0,0 +1,14 @@ +import { connect } from 'react-redux' + +import { updateQueue } from '../actions/queue' +import QueueMessageEnabledToggle from '../components/QueueMessageEnabledToggle' + +const mapDispatchToProps = dispatch => ({ + updateQueue: (queueId, attributes) => + dispatch(updateQueue(queueId, attributes)), +}) + +export default connect( + null, + mapDispatchToProps +)(QueueMessageEnabledToggle) diff --git a/src/migrations/20181009234426-queue-message.js b/src/migrations/20181009234426-queue-message.js new file mode 100644 index 00000000..8741545d --- /dev/null +++ b/src/migrations/20181009234426-queue-message.js @@ -0,0 +1,22 @@ +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface + .addColumn('queues', 'message', { + type: Sequelize.TEXT, + after: 'open', + }) + .then(() => { + return queryInterface.addColumn('queues', 'messageEnabled', { + type: Sequelize.BOOLEAN, + after: 'message', + defaultValue: false, + }) + }) + }, + + down: (queryInterface, _Sequelize) => { + return queryInterface.removeColumn('queues', 'message').then(() => { + return queryInterface.removeColumn('queues', 'messageEnabled') + }) + }, +} diff --git a/src/models/Queue.js b/src/models/Queue.js index e530e17e..c57de833 100644 --- a/src/models/Queue.js +++ b/src/models/Queue.js @@ -12,6 +12,11 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.BOOLEAN, defaultValue: true, }, + message: DataTypes.TEXT, + messageEnabled: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, startTime: DataTypes.DATE, endTime: DataTypes.DATE, }, diff --git a/src/pages/queue.js b/src/pages/queue.js index 5d314da0..507718c5 100644 --- a/src/pages/queue.js +++ b/src/pages/queue.js @@ -13,9 +13,11 @@ import Error from '../components/Error' import StaffSidebar from '../components/StaffSidebar' import QuestionPanelContainer from '../containers/QuestionPanelContainer' import QuestionListContainer from '../containers/QuestionListContainer' +import QueueMessageContainer from '../containers/QueueMessageContainer' import ShowForCourseStaff from '../components/ShowForCourseStaff' import QuestionNotificationsToggle from '../components/QuestionNotificationsToggle' import QueueStatusToggleContainer from '../containers/QueueStatusToggleContainer' +import QueueMessageEnabledToggleContainer from '../containers/QueueMessageEnabledToggleContainer' class Queue extends React.Component { static getInitialProps({ isServer, store, query }) { @@ -78,10 +80,14 @@ class Queue extends React.Component { + + {this.props.queue.messageEnabled && ( + + )} {this.props.queue.open && ( )} @@ -112,6 +118,8 @@ Queue.propTypes = { location: PropTypes.string, courseId: PropTypes.number, open: PropTypes.bool, + message: PropTypes.string, + messageEnabled: PropTypes.bool, }), pageTransitionReadyToEnter: PropTypes.func, }