diff --git a/docs/production/giphy-gif-integration.md b/docs/production/giphy-gif-integration.md new file mode 100644 index 0000000000..eea293985c --- /dev/null +++ b/docs/production/giphy-gif-integration.md @@ -0,0 +1,47 @@ +# GIPHY GIF integration + +This page documents the server-level configuration required to enable +GIPHY integration to [add GIFs in your message][help-center-giphy] on +a self-hosted Zulip server. + +To enable this integration, you need to get a production API key from +[GIPHY](https://developers.giphy.com/). + +## Apply for API key + +1. [Create a GIPHY account](https://giphy.com/join). + +1. Create a GIPHY API Key by clicking “Create an App” on the + [Developer Dashboard][giphy-dashboard]. + +1. Choose **SDK** as product type and click **Next Step**. + +1. Enter a name and a description for your app and click on **Create + New App**. The hostname for your Zulip server is a fine name. + +1. You will receive a beta API key. Apply for a production API key + by following the steps mentioned by GIPHY on the same page. + +You can then configure your Zulip server to use GIPHY API as +follows: + +1. In `/etc/zulip/settings.py`, enter your GIPHY API key as + `GIPHY_API_KEY`. + + GIPHY API keys are not secrets -- GIPHY expects every browser or + other client connecting to your Zulip server will receive a copy -- + which is why they are configured in `settings.py` and not + `zulip-secrets.conf`. + +1. Restart the Zulip server with + `/home/zulip/deployments/current/scripts/restart-server`. + +Congratulations! You've configured the GIPHY integration for your +Zulip server. Your users can now use the integration as described in +[the help center article][help-center-giphy]. (A browser reload may +be required). + + + +[help-center-giphy]: https://zulip.com/help/animated-gifs-from-giphy +[giphy-dashboard]: https://developers.giphy.com/dashboard/ diff --git a/docs/production/index.rst b/docs/production/index.rst index 1910289db2..9415100024 100644 --- a/docs/production/index.rst +++ b/docs/production/index.rst @@ -22,3 +22,4 @@ Zulip in production deployment email-gateway video-calls + giphy-gif-integration diff --git a/package.json b/package.json index 7ec113a291..660a283c54 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-typescript": "^7.3.3", "@babel/register": "^7.6.2", + "@giphy/js-components": "^4.3.1", + "@giphy/js-fetch-api": "^2.4.0", "@uppy/core": "^1.7.1", "@uppy/progress-bar": "^1.3.4", "@uppy/xhr-upload": "^1.4.2", diff --git a/static/images/GIPHY_attribution.png b/static/images/GIPHY_attribution.png new file mode 100644 index 0000000000..31527b6584 Binary files /dev/null and b/static/images/GIPHY_attribution.png differ diff --git a/static/images/GIPHY_big_logo.png b/static/images/GIPHY_big_logo.png new file mode 100644 index 0000000000..70a004c250 Binary files /dev/null and b/static/images/GIPHY_big_logo.png differ diff --git a/static/images/GIPHY_logo.png b/static/images/GIPHY_logo.png new file mode 100644 index 0000000000..8707629921 Binary files /dev/null and b/static/images/GIPHY_logo.png differ diff --git a/static/images/GIPHY_zulip.png b/static/images/GIPHY_zulip.png new file mode 100644 index 0000000000..991ea0cfb5 Binary files /dev/null and b/static/images/GIPHY_zulip.png differ diff --git a/static/js/click_handlers.js b/static/js/click_handlers.js index 1b3b487d2e..38ab9ed0e2 100644 --- a/static/js/click_handlers.js +++ b/static/js/click_handlers.js @@ -16,6 +16,7 @@ import * as compose from "./compose"; import * as compose_actions from "./compose_actions"; import * as compose_state from "./compose_state"; import * as emoji_picker from "./emoji_picker"; +import * as giphy from "./giphy"; import * as hash_util from "./hash_util"; import * as hotspots from "./hotspots"; import {i18n} from "./i18n"; @@ -656,6 +657,49 @@ export function initialize() { // COMPOSE + // Allow inserting GIFs in compose box using enter keypress. + $("body").on("keydown", ".giphy-gif", convert_enter_to_click); + + $("#compose_giphy_logo").on("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + + if ($("#giphy_grid_in_popover").length) { + $("#compose_box_giphy_grid").popover("hide"); + return; + } + $("#compose_box_giphy_grid").popover("show"); + let gifs_grid = giphy.renderGIPHYGrid($("#giphy_grid_in_popover .popover-content")[0]); + + $("body").on( + "keyup", + "#giphy-search-query", + // Use debounce to create a 300ms interval between + // every search. This makes the UX of searching pleasant + // by allowing user to finish typing before search + // is executed. + _.debounce(() => { + // GIPHY popover may have been hidden by the + // time this function is called. + if (gifs_grid) { + gifs_grid.remove(); + gifs_grid = giphy.update_grid_with_search_term(); + } + }, 300), + ); + + $(document).one("compose_canceled.zulip compose_finished.zulip", () => { + $("#compose_box_giphy_grid").popover("hide"); + }); + + // Focus on search box by default. + // This is specially helpful for users + // navigating via keybaord. + $("#giphy-search-query").trigger("focus"); + }); + + $("body").on("keydown", "#compose_giphy_logo", convert_enter_to_click); + // NB: This just binds to current elements, and won't bind to elements // created after ready() is called. $("#compose-send-status .compose-send-status-close").on("click", () => { diff --git a/static/js/giphy.js b/static/js/giphy.js new file mode 100644 index 0000000000..b22058299c --- /dev/null +++ b/static/js/giphy.js @@ -0,0 +1,99 @@ +import {renderGrid} from "@giphy/js-components"; +import {GiphyFetch} from "@giphy/js-fetch-api"; +import $ from "jquery"; +import _ from "lodash"; + +import render_giphy_picker from "../templates/giphy_picker.hbs"; +import render_giphy_picker_mobile from "../templates/giphy_picker_mobile.hbs"; + +import * as compose_ui from "./compose_ui"; +import {page_params} from "./page_params"; + +const giphy_fetch = new GiphyFetch(page_params.giphy_api_key); +let search_term = ""; + +function fetchGifs(offset) { + const config = { + offset, + limit: 25, + // Default rating to 'g' until we can make this configurable. + rating: "g", + // We don't pass random_id here, for privacy reasons. + }; + if (search_term === "") { + // Get the trending gifs by default. + return giphy_fetch.trending(config); + } + return giphy_fetch.search(search_term, config); +} + +export function renderGIPHYGrid(targetEl) { + const render = () => + // See https://github.com/Giphy/giphy-js/blob/master/packages/components/README.md#grid + // for detailed documentation. + renderGrid( + { + width: 300, + fetchGifs, + columns: 3, + gutter: 6, + noLink: true, + // Hide the creator attribution that appears over a + // GIF; nice in principle but too distracting. + hideAttribution: true, + onGifClick: (props) => { + $("#compose_box_giphy_grid").popover("hide"); + compose_ui.insert_syntax_and_focus(`[](${props.images.downsized_medium.url})`); + }, + onGifVisible: (gif, e) => { + // Set tabindex for all the GIFs that + // are visible to the user. This allows + // user to navigate the GIFs using tab. + // TODO: Remove this after https://github.com/Giphy/giphy-js/issues/174 + // is closed. + e.target.tabIndex = 0; + }, + }, + targetEl, + ); + + // Limit the rate at which we do queries to the GIPHY API to + // one per 300ms, in line with animation timing, basically to avoid + // content appearing while the user is typing. + const resizeRender = _.throttle(render, 300); + window.addEventListener("resize", resizeRender, false); + const remove = render(); + return { + remove: () => { + remove(); + window.removeEventListener("resize", resizeRender, false); + }, + }; +} + +let template = render_giphy_picker(); + +if (window.innerWidth <= 768) { + // Show as modal in the center for small screens. + template = render_giphy_picker_mobile(); +} + +$("#compose_box_giphy_grid").popover({ + animation: true, + placement: "top", + html: true, + trigger: "manual", + template, +}); + +export function update_grid_with_search_term() { + const search_elem = $("#giphy-search-query"); + // GIPHY popover may have been hidden by the + // time this function is called. + if (search_elem.length) { + search_term = search_elem[0].value; + return renderGIPHYGrid($("#giphy_grid_in_popover .popover-content")[0]); + } + // Return undefined to stop searching. + return undefined; +} diff --git a/static/js/hotkey.js b/static/js/hotkey.js index bf6f3868e3..e3cdd5d751 100644 --- a/static/js/hotkey.js +++ b/static/js/hotkey.js @@ -275,6 +275,13 @@ export function process_escape_key(e) { } if (compose_state.composing()) { + // Hide GIPHY popover if it's open. + if ($("#giphy_grid_in_popover").length) { + $("#compose_box_giphy_grid").popover("hide"); + $("#compose-textarea").trigger("focus"); + return true; + } + // Check for errors in compose box; close errors if they exist if ($("#compose-send-status").css("display") !== "none") { $("#compose-send-status").hide(); diff --git a/static/styles/compose.css b/static/styles/compose.css index 145fd7ab2a..658294ca50 100644 --- a/static/styles/compose.css +++ b/static/styles/compose.css @@ -398,6 +398,12 @@ input.recipient_box { display: inline-block; } +#compose_box_giphy_grid img { + width: auto; + height: 14px; + vertical-align: top; +} + a.message-control-button { display: block; opacity: 0.4; diff --git a/static/styles/popovers.css b/static/styles/popovers.css index 012fff8837..5c96299f3f 100644 --- a/static/styles/popovers.css +++ b/static/styles/popovers.css @@ -449,6 +449,57 @@ ul { } } +.popover.top .arrow::after { + left: -1px; +} + +#giphy_grid_in_popover { + /* 300px of GIPHY grid + 15px scroll bar width */ + width: 315px; + + .giphy-gif { + cursor: pointer; + } + + .giphy-gif-img:focus { + /* Red outline for clear visibility + * of which image is in foucs. + */ + outline-color: hsl(0, 100%, 50%); + } + + .search-box { + display: flex; + position: sticky; + + input { + flex-grow: 1; + margin: 5px; + } + } + + .popover-content { + overflow: auto; + height: 200px; + } + + .popover-footer { + text-align: center; + + img { + width: 120px; + } + } +} + +@media (max-width: 768px) { + #giphy_grid_in_popover { + position: static; + top: 0 !important; + left: 0 !important; + } +} + .user_info_status_text { opacity: 0.8; padding: 2px 0 3px 0; diff --git a/static/templates/giphy_picker.hbs b/static/templates/giphy_picker.hbs new file mode 100644 index 0000000000..ecd5601cc7 --- /dev/null +++ b/static/templates/giphy_picker.hbs @@ -0,0 +1,12 @@ +
+ Enjoy animated GIFs with Zulip's native GIPHY + integration. +
+Zulip is open source, so if something important for
diff --git a/templates/zerver/help/animated-gifs-from-giphy.md b/templates/zerver/help/animated-gifs-from-giphy.md
new file mode 100644
index 0000000000..b6cdf7bfce
--- /dev/null
+++ b/templates/zerver/help/animated-gifs-from-giphy.md
@@ -0,0 +1,32 @@
+# Animated GIFs from GIPHY
+
+Zulip integrates with [GIPHY](https://giphy.com), allowing you to
+conveniently search for animated GIFs and include them in your
+messages.
+
+Be thoughtful when using this feature! Animated GIFs can be fun, but
+they can also distract from the content of a conversation.
+
+1. First, [open the compose box](/help/open-the-compose-box).
+1. **Click the GIPHY logo** at the bottom of the compose box. This
+ opens the GIPHY search tool.
+1. Use the search tool to find a GIF you'd like to use.
+1. **Click on an image** to insert a link to the GIF in the compose box.
+1. Send the message. Zulip will display the GIF like any other linked
+ image.
+
+You can [preview the
+message](/help/preview-your-message-before-sending) before sending to
+see what the message will look like.
+
+## Troubleshooting
+
+* If you don't see the GIPHY icon, this is likely because you are
+ using a self-hosted Zulip server that has not [configured the GIPHY
+ integration][configure-giphy].
+
+[configure-giphy]: https://zulip.readthedocs.io/en/latest/production/giphy-gif-integration.html
+
+* If your GIFs only appear as links after sending them, this is likely
+because the organization has disabled [previews of linked
+images](/help/allow-image-link-previews).
diff --git a/templates/zerver/help/include/sidebar_index.md b/templates/zerver/help/include/sidebar_index.md
index 3cfb3109c1..7ef951f0f8 100644
--- a/templates/zerver/help/include/sidebar_index.md
+++ b/templates/zerver/help/include/sidebar_index.md
@@ -37,6 +37,7 @@
* [Mention a user or group](/help/mention-a-user-or-group)
* [Edit or delete a message](/help/edit-or-delete-a-message)
* [Message a stream by email](/help/message-a-stream-by-email)
+* [Add GIFs in your message](/help/animated-gifs-from-giphy)
## Reading messages
* [Reading strategies](/help/reading-strategies)
diff --git a/templates/zerver/integrations/giphy.md b/templates/zerver/integrations/giphy.md
new file mode 100644
index 0000000000..895bfed45f
--- /dev/null
+++ b/templates/zerver/integrations/giphy.md
@@ -0,0 +1,14 @@
+# GIPHY GIF integration
+
+Send animated GIFs with your message using GIPHY integration.
+
+![](/static/images/GIPHY_zulip.png)
+
+GIPHY is enabled by default in Zulip Cloud. If the you're on a
+self-hosted server where the [GIPHY integration][help-center-giphy]
+isn't already configured, see the [configuration
+instructions][configure-giphy] to enable it.
+
+
+[help-center-giphy]: /help/animated-gifs-from-giphy
+[configure-giphy]: https://zulip.readthedocs.io/en/latest/production/giphy-gif-integration.html
diff --git a/tools/lib/capitalization.py b/tools/lib/capitalization.py
index 2bd50f65c3..aeb780f60c 100644
--- a/tools/lib/capitalization.py
+++ b/tools/lib/capitalization.py
@@ -157,6 +157,9 @@ IGNORED_PHRASES = [
r"your-organization-url",
# Used in invite modal
r"or",
+ # Used in GIPHY popover.
+ r"GIFs",
+ r"GIPHY",
]
# Sort regexes in descending order of their lengths. As a result, the
diff --git a/tools/test-js-with-node b/tools/test-js-with-node
index 4d73176b2d..ed380a6b6b 100755
--- a/tools/test-js-with-node
+++ b/tools/test-js-with-node
@@ -63,6 +63,7 @@ EXEMPT_FILES = {
"static/js/feedback_widget.js",
"static/js/floating_recipient_bar.js",
"static/js/gear_menu.js",
+ "static/js/giphy.js",
"static/js/global.d.ts",
"static/js/hashchange.js",
"static/js/hbs.d.ts",
diff --git a/version.py b/version.py
index 124fbee8cc..6bd2c7873f 100644
--- a/version.py
+++ b/version.py
@@ -30,7 +30,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
#
# Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md.
-API_FEATURE_LEVEL = 46
+API_FEATURE_LEVEL = 47
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump
@@ -45,4 +45,4 @@ API_FEATURE_LEVEL = 46
# historical commits sharing the same major version, in which case a
# minor version bump suffices.
-PROVISION_VERSION = "135.0"
+PROVISION_VERSION = "135.1"
diff --git a/yarn.lock b/yarn.lock
index 73c796cd18..b3a13e96cc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -197,7 +197,7 @@
dependencies:
"@babel/types" "^7.13.0"
-"@babel/helper-module-imports@^7.12.13":
+"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0"
integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==
@@ -876,10 +876,10 @@
pirates "^4.0.0"
source-map-support "^0.5.16"
-"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
- version "7.13.9"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee"
- integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
+ version "7.13.10"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
+ integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
dependencies:
regenerator-runtime "^0.13.4"
@@ -923,6 +923,62 @@
dependencies:
commander "^2.15.1"
+"@emotion/cache@^10.0.27":
+ version "10.0.29"
+ resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
+ integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==
+ dependencies:
+ "@emotion/sheet" "0.9.4"
+ "@emotion/stylis" "0.8.5"
+ "@emotion/utils" "0.11.3"
+ "@emotion/weak-memoize" "0.2.5"
+
+"@emotion/hash@0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
+ integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
+
+"@emotion/memoize@0.7.4":
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
+ integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
+
+"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16":
+ version "0.11.16"
+ resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad"
+ integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==
+ dependencies:
+ "@emotion/hash" "0.8.0"
+ "@emotion/memoize" "0.7.4"
+ "@emotion/unitless" "0.7.5"
+ "@emotion/utils" "0.11.3"
+ csstype "^2.5.7"
+
+"@emotion/sheet@0.9.4":
+ version "0.9.4"
+ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5"
+ integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==
+
+"@emotion/stylis@0.8.5":
+ version "0.8.5"
+ resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
+ integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
+
+"@emotion/unitless@0.7.5":
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
+ integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
+
+"@emotion/utils@0.11.3":
+ version "0.11.3"
+ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924"
+ integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
+
+"@emotion/weak-memoize@0.2.5":
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
+ integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
+
"@eslint/eslintrc@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547"
@@ -938,6 +994,62 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
+"@giphy/js-analytics@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@giphy/js-analytics/-/js-analytics-3.0.0.tgz#7d7a65faaa3fb997406da4a682f76ad79d392c04"
+ integrity sha512-WeiU+MVyc7vQD6VTztBl1ZAByTKqOpHRxlZHR3PQjzasc1damHooNcHyHqd8PrjYC3Ag/t/3IuFzsFWVh/dDNA==
+ dependencies:
+ "@giphy/js-types" "^3.1.0"
+ "@giphy/js-util" "^2.2.0"
+ append-query "^2.1.0"
+ throttle-debounce "^2.3.0"
+
+"@giphy/js-brand@^2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@giphy/js-brand/-/js-brand-2.0.2.tgz#bd40f63b1de9cb60b4116a78daa8b6c523e6954e"
+ integrity sha512-q2zDuVaDgobc5o9ucoFdYArrVKfyKXo0/jddvVgAazACLEPycAcxfqdyc98TRELwTVcmN/e0ULfLjim2lq3WXw==
+ dependencies:
+ emotion "10.0.27"
+
+"@giphy/js-components@^4.3.1":
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/@giphy/js-components/-/js-components-4.3.1.tgz#93aaf3aa2d85c2cfc21f6a6d1d06df6a026f2040"
+ integrity sha512-DByKMgivmuJFrt5zUNnn5r4dtfqhjAj9H76/r15rEXJdtSXz+mdbp89yt0DTQnPQJMP93X1owaFu2PBA9UX/Cg==
+ dependencies:
+ "@giphy/js-analytics" "^3.0.0"
+ "@giphy/js-brand" "^2.0.2"
+ "@giphy/js-fetch-api" "^2.4.0"
+ "@giphy/js-types" "^3.1.0"
+ "@giphy/js-util" "^2.2.0"
+ bricks.js "^1.8.0"
+ emotion "10.0.27"
+ intersection-observer "^0.11.0"
+ preact "10.4.8"
+ throttle-debounce "^2.3.0"
+
+"@giphy/js-fetch-api@^2.4.0":
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/@giphy/js-fetch-api/-/js-fetch-api-2.4.0.tgz#e96c7a2599b720d3e40ea409d8e4c4a692aa4611"
+ integrity sha512-xiMHnv81XZjgut4yrkHB5QHDWGNhVHoyMDb3kQBy5H0NruwtQ+aM5BE9xbP+XQlxt3eRnPRJcLy5ORk9+K0fIQ==
+ dependencies:
+ "@giphy/js-types" "^3.1.0"
+ "@giphy/js-util" "^2.2.0"
+ qs "^6.9.4"
+
+"@giphy/js-types@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@giphy/js-types/-/js-types-3.1.0.tgz#cfddcfeb7d389542fc2ffc147bbe122760cfa3b6"
+ integrity sha512-/gzA5Ny6tYVSXHndBzoIPx2t0Lu0UkbvCxb4g+KWEwQslWmak/2vZcO2X6jdIObHkN/8ejFIXr5Od40Namhtvw==
+
+"@giphy/js-util@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@giphy/js-util/-/js-util-2.2.0.tgz#887f957e497ec7a956db5bc271259a02e7cbeffd"
+ integrity sha512-3EHFgOCtAwyV+yild+2YhGB9I+Ht1rXZw+kAuL4+9Ypy+oOwsK++iSdeAw6dY/nddzm91Ch3KB1rP1DBvU+JuA==
+ dependencies:
+ "@giphy/js-types" "^3.1.0"
+ dompurify "^2.2.2"
+ uuid "^8.3.0"
+
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1924,6 +2036,13 @@ append-buffer@^1.0.2:
dependencies:
buffer-equal "^1.0.0"
+append-query@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/append-query/-/append-query-2.1.0.tgz#c3392d2ddf8c70d3f582a14b051b12333da540bf"
+ integrity sha512-v9YXX6No91QkRA31IvTjl+1kFU+A0DaOzZpCNR7Q/ZoPSXlo5a4W2eS0qos6AKvCKL4MnNna78xgqIG8NRvQwg==
+ dependencies:
+ extend "^2.0.0"
+
append-transform@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12"
@@ -2214,6 +2333,31 @@ babel-plugin-dynamic-import-node@^2.3.3:
dependencies:
object.assign "^4.1.0"
+babel-plugin-emotion@^10.0.27:
+ version "10.2.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz#a1fe3503cff80abfd0bdda14abd2e8e57a79d17d"
+ integrity sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.0.0"
+ "@emotion/hash" "0.8.0"
+ "@emotion/memoize" "0.7.4"
+ "@emotion/serialize" "^0.11.16"
+ babel-plugin-macros "^2.0.0"
+ babel-plugin-syntax-jsx "^6.18.0"
+ convert-source-map "^1.5.0"
+ escape-string-regexp "^1.0.5"
+ find-root "^1.1.0"
+ source-map "^0.5.7"
+
+babel-plugin-macros@^2.0.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
+ integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ cosmiconfig "^6.0.0"
+ resolve "^1.12.0"
+
babel-plugin-polyfill-corejs2@^0.1.4:
version "0.1.10"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.10.tgz#a2c5c245f56c0cac3dbddbf0726a46b24f0f81d1"
@@ -2243,6 +2387,11 @@ babel-plugin-rewire-ts@^1.4.0:
resolved "https://registry.yarnpkg.com/babel-plugin-rewire-ts/-/babel-plugin-rewire-ts-1.4.0.tgz#6c814d5aa3ef3d8c0d0c7a27891877fb28930e5b"
integrity sha512-XVyyWMIx1fNSG42vbUaAro1LANLs/fBW6KurYaeoVjS2U8zLCaow7LKll6zjs1cwcqcbZK2v59zVouPs+JAqxw==
+babel-plugin-syntax-jsx@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
+ integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
+
bail@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
@@ -2467,6 +2616,13 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
+bricks.js@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/bricks.js/-/bricks.js-1.8.0.tgz#8fdeb3c0226af251f4d5727a7df7f9ac0092b4b2"
+ integrity sha1-j96zwCJq8lH01XJ6fff5rACStLI=
+ dependencies:
+ knot.js "^1.1.5"
+
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -3472,6 +3628,17 @@ cosmiconfig@^5.0.0:
js-yaml "^3.13.1"
parse-json "^4.0.0"
+cosmiconfig@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
+ integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.7.2"
+
cosmiconfig@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3"
@@ -3496,6 +3663,16 @@ create-ecdh@^4.0.0:
bn.js "^4.1.0"
elliptic "^6.5.3"
+create-emotion@^10.0.27:
+ version "10.0.27"
+ resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-10.0.27.tgz#cb4fa2db750f6ca6f9a001a33fbf1f6c46789503"
+ integrity sha512-fIK73w82HPPn/RsAij7+Zt8eCE8SptcJ3WoRMfxMtjteYxud8GDTKKld7MYwAX2TVhrw29uR1N/bVGxeStHILg==
+ dependencies:
+ "@emotion/cache" "^10.0.27"
+ "@emotion/serialize" "^0.11.15"
+ "@emotion/sheet" "0.9.4"
+ "@emotion/utils" "0.11.3"
+
create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
@@ -3796,6 +3973,11 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
+csstype@^2.5.7:
+ version "2.6.16"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.16.tgz#544d69f547013b85a40d15bff75db38f34fe9c39"
+ integrity sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q==
+
cubic-hermite@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cubic-hermite/-/cubic-hermite-1.0.0.tgz#84e3b2f272b31454e8393b99bb6aed45168c14e5"
@@ -4262,6 +4444,11 @@ domino@^2.1.6:
resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe"
integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==
+dompurify@^2.2.2:
+ version "2.2.7"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.7.tgz#a5f055a2a471638680e779bd08fc334962d11fd8"
+ integrity sha512-jdtDffdGNY+C76jvodNTu9jt5yYj59vuTUyx+wXdzcSwAGTYZDAQkQ7Iwx9zcGrA4ixC1syU4H3RZROqRxokxg==
+
domutils@^1.5.1, domutils@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
@@ -4422,6 +4609,14 @@ emojis-list@^3.0.0:
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
+emotion@10.0.27:
+ version "10.0.27"
+ resolved "https://registry.yarnpkg.com/emotion/-/emotion-10.0.27.tgz#f9ca5df98630980a23c819a56262560562e5d75e"
+ integrity sha512-2xdDzdWWzue8R8lu4G76uWX5WhyQuzATon9LmNeCy/2BHVC6dsEpfhN1a0qhELgtDVdjyEA6J8Y/VlI5ZnaH0g==
+ dependencies:
+ babel-plugin-emotion "^10.0.27"
+ create-emotion "^10.0.27"
+
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@@ -4972,6 +5167,11 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
+extend@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-2.0.2.tgz#1b74985400171b85554894459c978de6ef453ab7"
+ integrity sha512-AgFD4VU+lVLP6vjnlNfF7OeInLTyeyckCNPEsuxz1vi786UuK/nk6ynPuhn/h+Ju9++TQyr5EpLRI14fc1QtTQ==
+
extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@@ -6675,7 +6875,7 @@ import-fresh@^2.0.0:
caller-path "^2.0.0"
resolve-from "^3.0.0"
-import-fresh@^3.0.0, import-fresh@^3.2.1:
+import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -6777,6 +6977,11 @@ interpret@^1.2.0, interpret@^1.4.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
+intersection-observer@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.11.0.tgz#f4ea067070326f68393ee161cc0a2ca4c0040c6f"
+ integrity sha512-KZArj2QVnmdud9zTpKf279m2bbGfG+4/kn16UU0NL3pTVl52ZHiJ9IRNSsnn6jaHrL9EGLFM5eWjTx2fz/+zoQ==
+
interval-tree-1d@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/interval-tree-1d/-/interval-tree-1d-1.0.3.tgz#8fdbde02b6b2c7dbdead636bcbed8e08710d85c1"
@@ -7602,6 +7807,11 @@ klona@^2.0.4:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
+knot.js@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/knot.js/-/knot.js-1.1.5.tgz#28e72522f703f50fe98812fde224dd72728fef5d"
+ integrity sha1-KOclIvcD9Q/piBL94iTdcnKP710=
+
known-css-properties@^0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.21.0.tgz#15fbd0bbb83447f3ce09d8af247ed47c68ede80d"
@@ -10010,6 +10220,11 @@ potpack@^1.0.1:
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.1.tgz#d1b1afd89e4c8f7762865ec30bd112ab767e2ebf"
integrity sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw==
+preact@10.4.8:
+ version "10.4.8"
+ resolved "https://registry.yarnpkg.com/preact/-/preact-10.4.8.tgz#8517b106cc5591eb675237c93da99ac052cf4756"
+ integrity sha512-uVLeEAyRsCkUEFhVHlOu17OxcrwC7+hTGZ08kBoLBiGHiZooUZuibQnphgMKftw/rqYntNMyhVCPqQhcyAGHag==
+
preact@8.2.9:
version "8.2.9"
resolved "https://registry.yarnpkg.com/preact/-/preact-8.2.9.tgz#813ba9dd45e5d97c5ea0d6c86d375b3be711cc40"
@@ -10195,6 +10410,13 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
+qs@^6.9.4:
+ version "6.10.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.0.tgz#8b6519121ab291c316a3e4d49cecf6d13d8c7fe5"
+ integrity sha512-yjACOWijC6L/kmPZZAsVBNY2zfHSIbpdpL977quseu56/8BZ2LoF5axK2bGhbzhVKt7V9xgWTtpyLbxwIoER0Q==
+ dependencies:
+ side-channel "^1.0.4"
+
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -10811,7 +11033,7 @@ resolve@^0.6.1:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-0.6.3.tgz#dd957982e7e736debdf53b58a4dd91754575dd46"
integrity sha1-3ZV5gufnNt699TtYpN2RdUV13UY=
-resolve@^1.0.0, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0:
+resolve@^1.0.0, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
@@ -11225,6 +11447,15 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@@ -11426,7 +11657,7 @@ source-map@0.5.6:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
-source-map@^0.5.0, source-map@^0.5.6:
+source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -12225,6 +12456,11 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
+throttle-debounce@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2"
+ integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==
+
through2-filter@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
@@ -12968,6 +13204,11 @@ uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^8.3.0:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1, v8-compile-cache@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
@@ -13594,10 +13835,10 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-yaml@^1.10.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
- integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
+yaml@^1.10.0, yaml@^1.7.2:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^13.1.2:
version "13.1.2"
diff --git a/zerver/lib/events.py b/zerver/lib/events.py
index 23f2a38586..83f6dfd647 100644
--- a/zerver/lib/events.py
+++ b/zerver/lib/events.py
@@ -444,6 +444,18 @@ def fetch_initial_state_data(
if want("video_calls"):
state["has_zoom_token"] = settings_user.zoom_token is not None
+ if want("giphy"):
+ # Normally, it would be a nasty security bug to send a
+ # server's API key to end users. However, GIPHY's API key
+ # security model is precisely to do that; every service
+ # publishes its API key (and GIPHY's client-side JS libraries
+ # require the API key to work). This security model makes
+ # sense because GIPHY API keys are all essentially equivalent
+ # in letting one search for GIFs; GIPHY only requires API keys
+ # to exist at all so that they can deactivate them in cases of
+ # abuse.
+ state["giphy_api_key"] = settings.GIPHY_API_KEY if settings.GIPHY_API_KEY else ""
+
return state
diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py
index 910371f9a3..e35f3f9068 100644
--- a/zerver/lib/integrations.py
+++ b/zerver/lib/integrations.py
@@ -470,6 +470,14 @@ INTEGRATIONS: Dict[str, Integration] = {
"errbot": Integration(
"errbot", "errbot", ["meta-integration", "bots"], doc="zerver/integrations/errbot.md"
),
+ "giphy": Integration(
+ "giphy",
+ "giphy",
+ display_name="GIPHY",
+ categories=["misc"],
+ doc="zerver/integrations/giphy.md",
+ logo="images/GIPHY_big_logo.png",
+ ),
"git": Integration(
"git", "git", ["version-control"], stream_name="commits", doc="zerver/integrations/git.md"
),
diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml
index 110f54ea22..90c6f45af6 100644
--- a/zerver/openapi/zulip.yaml
+++ b/zerver/openapi/zulip.yaml
@@ -7115,6 +7115,19 @@ paths:
A boolean which signifies whether the user has a zoom token and has thus completed
OAuth flow for the [Zoom integration](/help/start-a-call). Clients need
to know whether initiating Zoom OAuth is required before creating a Zoom call.
+ giphy_api_key:
+ type: string
+ description: |
+ Present if `giphy` is present in `fetch_event_types`.
+
+ GIPHY's client-side SDKs needs this API key to use the GIPHY API.
+ GIPHY API keys are not secret (their main purpose appears to be
+ allowing GIPHY to block a problematic app). Please don't use our API
+ key for an app unrelated to Zulip.
+
+ Developers of clients should also read the
+ [GIPHY API TOS](https://support.giphy.com/hc/en-us/articles/360028134111-GIPHY-API-Terms-of-Service-)
+ before using this API key.
enable_desktop_notifications:
type: boolean
description: |
diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py
index f5f4b031a7..4261f35918 100644
--- a/zerver/tests/test_event_system.py
+++ b/zerver/tests/test_event_system.py
@@ -882,6 +882,7 @@ class FetchQueriesTest(ZulipTestCase):
update_message_flags=5,
user_status=1,
video_calls=0,
+ giphy=0,
)
wanted_event_types = {item[0][0] for item in want_mock.call_args_list}
diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py
index 904e3d9080..02658b81ec 100644
--- a/zerver/tests/test_home.py
+++ b/zerver/tests/test_home.py
@@ -83,6 +83,7 @@ class HomeTest(ZulipTestCase):
"fluid_layout_width",
"full_name",
"furthest_read_time",
+ "giphy_api_key",
"has_mobile_devices",
"has_zoom_token",
"high_contrast_mode",
diff --git a/zerver/views/home.py b/zerver/views/home.py
index c487edbb32..9059210951 100644
--- a/zerver/views/home.py
+++ b/zerver/views/home.py
@@ -243,6 +243,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
"embedded": narrow_stream is not None,
"invite_as": PreregistrationUser.INVITE_AS,
"max_file_upload_size_mib": settings.MAX_FILE_UPLOAD_SIZE,
+ "giphy_api_available": bool(page_params["giphy_api_key"]),
},
)
patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True)
diff --git a/zproject/default_settings.py b/zproject/default_settings.py
index 1dcd6e89ea..4da7233c92 100644
--- a/zproject/default_settings.py
+++ b/zproject/default_settings.py
@@ -133,6 +133,9 @@ MAX_FILE_UPLOAD_SIZE = 25
# Jitsi Meet video call integration; set to None to disable integration.
JITSI_SERVER_URL = "https://meet.jit.si"
+# GIPHY API key.
+GIPHY_API_KEY = get_secret("giphy_api_key")
+
# Allow setting BigBlueButton settings in zulip-secrets.conf in
# development; this is useful since there are no public BigBlueButton servers.
BIG_BLUE_BUTTON_URL = get_secret("big_blue_button_url", development_only=True)
diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py
index 2e444c0adc..9546554af4 100644
--- a/zproject/prod_settings_template.py
+++ b/zproject/prod_settings_template.py
@@ -563,6 +563,10 @@ SOCIAL_AUTH_SAML_SUPPORT_CONTACT = {
## system-level monitoring tools.
# LOGGING_SHOW_PID = False
+#################
+## Animated GIF integration powered by GIPHY. See:
+## https://zulip.readthedocs.io/en/latest/production/giphy-gif-integration.html
+# GIPHY_API_KEY = "