diff --git a/package-lock.json b/package-lock.json index 36b267e..1be62b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,15 +16,18 @@ "@prisma/client": "^3.11.1", "@tailwindcss/forms": "^0.4.0", "@tailwindcss/typography": "^0.5.1", + "@types/dom-to-image": "^2.6.4", "@types/jsdom": "^16.2.14", "@types/nprogress": "^0.2.0", "@types/react": "^17.0.39", + "@types/react-copy-to-clipboard": "^5.0.2", "airtable": "^0.11.1", "algoliasearch": "^4.10.3", "autoprefixer": "^10.1.0", "axios": "^0.25.0", "chroma-js": "^2.4.2", "critters": "^0.0.16", + "date-fns": "^2.28.0", "dom-to-image": "^2.6.0", "dotenv": "^16.0.0", "fetch": "^1.1.0", @@ -3157,6 +3160,11 @@ "@types/ms": "*" } }, + "node_modules/@types/dom-to-image": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.4.tgz", + "integrity": "sha512-UddUdGF1qulrSDulkz3K2Ypq527MR6ixlgAzqLbxSiQ0icx0XDlIV+h4+edmjq/1dqn0KgN0xGSe1kI9t+vGuw==" + }, "node_modules/@types/hast": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@types%2fhast/-/hast-2.3.4.tgz", @@ -3288,6 +3296,14 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@types%2freact-transition-group/-/react-transition-group-4.4.4.tgz", @@ -5250,10 +5266,16 @@ "dev": true }, "node_modules/date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==", + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } }, "node_modules/debounce": { "version": "1.2.1", @@ -8055,6 +8077,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "node_modules/listr-verbose-renderer/node_modules/date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, "node_modules/listr-verbose-renderer/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -42331,6 +42359,11 @@ "@types/ms": "*" } }, + "@types/dom-to-image": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.4.tgz", + "integrity": "sha512-UddUdGF1qulrSDulkz3K2Ypq527MR6ixlgAzqLbxSiQ0icx0XDlIV+h4+edmjq/1dqn0KgN0xGSe1kI9t+vGuw==" + }, "@types/hast": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@types%2fhast/-/hast-2.3.4.tgz", @@ -42453,6 +42486,14 @@ "csstype": "^3.0.2" } }, + "@types/react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==", + "requires": { + "@types/react": "*" + } + }, "@types/react-transition-group": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@types%2freact-transition-group/-/react-transition-group-4.4.4.tgz", @@ -43924,10 +43965,9 @@ "dev": true }, "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==" }, "debounce": { "version": "1.2.1", @@ -45991,6 +46031,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", diff --git a/package.json b/package.json index 5cd4b0a..f5898ca 100644 --- a/package.json +++ b/package.json @@ -34,15 +34,18 @@ "@prisma/client": "^3.11.1", "@tailwindcss/forms": "^0.4.0", "@tailwindcss/typography": "^0.5.1", + "@types/dom-to-image": "^2.6.4", "@types/jsdom": "^16.2.14", "@types/nprogress": "^0.2.0", "@types/react": "^17.0.39", + "@types/react-copy-to-clipboard": "^5.0.2", "airtable": "^0.11.1", "algoliasearch": "^4.10.3", "autoprefixer": "^10.1.0", "axios": "^0.25.0", "chroma-js": "^2.4.2", "critters": "^0.0.16", + "date-fns": "^2.28.0", "dom-to-image": "^2.6.0", "dotenv": "^16.0.0", "fetch": "^1.1.0", diff --git a/src/backend/frontpage.ts b/src/backend/frontpage.ts index 2197b8a..4965cd9 100644 --- a/src/backend/frontpage.ts +++ b/src/backend/frontpage.ts @@ -19,11 +19,14 @@ export async function getFrontpage(): Promise { export async function rebuildFrontpage() { await measureTime(async () => { const rows = await prisma.$queryRaw<{ id: string }[]>` - SELECT id FROM questions + SELECT questions.id FROM questions, history WHERE - (qualityindicators->>'stars')::int >= 3 - AND description != '' - AND JSONB_ARRAY_LENGTH(options) > 0 + questions.id = history.id + AND (questions.qualityindicators->>'stars')::int >= 3 + AND questions.description != '' + AND JSONB_ARRAY_LENGTH(questions.options) > 0 + GROUP BY questions.id + HAVING COUNT(DISTINCT history.timestamp) >= 7 ORDER BY RANDOM() LIMIT 50 `; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d3c85a1..a1b09c8 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,25 +1,16 @@ import { NextPage } from "next"; import React from "react"; -import { displayQuestionsWrapperForSearch } from "../web/display/displayQuestionsWrappers"; import { Layout } from "../web/display/Layout"; import { Props } from "../web/search/anySearchPage"; -import CommonDisplay from "../web/search/CommonDisplay"; +import { CommonDisplay } from "../web/search/CommonDisplay"; export { getServerSideProps } from "../web/search/anySearchPage"; const IndexPage: NextPage = (props) => { return ( - + ); }; diff --git a/src/web/common/CopyParagraph.tsx b/src/web/common/CopyParagraph.tsx new file mode 100644 index 0000000..ed14abf --- /dev/null +++ b/src/web/common/CopyParagraph.tsx @@ -0,0 +1,35 @@ +import { useState } from "react"; +import { CopyToClipboard } from "react-copy-to-clipboard"; + +import { Button } from "../display/Button"; + +// https://stackoverflow.com/questions/39501289/in-reactjs-how-to-copy-text-to-clipboard + +export const CopyParagraph: React.FC<{ text: string; buttonText: string }> = ({ + text, + buttonText: initialButtonText, +}) => { + const [buttonText, setButtonText] = useState(initialButtonText); + const handleButton = () => { + setButtonText("Copied"); + setTimeout(async () => { + setButtonText(initialButtonText); + }, 2000); + }; + return ( +
+

{ + e.preventDefault(); + navigator.clipboard.writeText(text); + }} + > + {text} +

+ + + +
+ ); +}; diff --git a/src/web/common/CopyText.tsx b/src/web/common/CopyText.tsx index 856d255..4294961 100644 --- a/src/web/common/CopyText.tsx +++ b/src/web/common/CopyText.tsx @@ -5,6 +5,8 @@ interface Props { displayText: string; } +// https://stackoverflow.com/questions/39501289/in-reactjs-how-to-copy-text-to-clipboard + export const CopyText: React.FC = ({ text, displayText }) => (
{} +interface Props extends React.ButtonHTMLAttributes { + size?: "small" | "normal"; +} -export const Button: React.FC = ({ children, ...rest }) => ( - -); +export const Button: React.FC = ({ + children, + size = "normal", + ...rest +}) => { + const padding = size === "normal" ? "px-5 py-4" : "px-3 py-2"; + return ( + + ); +}; diff --git a/src/web/display/DisplayOneQuestionForCapture.tsx b/src/web/display/DisplayOneQuestionForCapture.tsx deleted file mode 100644 index adcad5e..0000000 --- a/src/web/display/DisplayOneQuestionForCapture.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import domtoimage from "dom-to-image"; // https://github.com/tsayen/dom-to-image -import { useEffect, useRef, useState } from "react"; -import { CopyToClipboard } from "react-copy-to-clipboard"; - -import { QuestionFragment } from "../fragments.generated"; -import { uploadToImgur } from "../worker/uploadToImgur"; -import { DisplayQuestion } from "./DisplayQuestion"; - -function displayOneQuestionInner(result: QuestionFragment, containerRef) { - return ( -
- {result ? ( - - ) : null} -
- ); -} - -let domToImageWrapper = (reactRef) => { - let node = reactRef.current; - const scale = 3; // Increase for better quality - const style = { - transform: "scale(" + scale + ")", - transformOrigin: "top left", - width: node.offsetWidth + "px", - height: node.offsetHeight + "px", - }; - const param = { - height: node.offsetHeight * scale, - width: node.offsetWidth * scale, - quality: 1, - style, - }; - let image = domtoimage.toPng(node, param); - return image; -}; - -let generateHtml = (result, imgSrc) => { - let html = `Metaforecast.org snapshot of ''${result.title}'', from ${result.platform}`; - return html; -}; - -let generateMarkdown = (result, imgSrc) => { - let markdown = `[![](${imgSrc})](${result.url})`; - return markdown; -}; - -let generateSource = (result, imgSrc, hasDisplayBeenCaptured) => { - const [htmlButtonStatus, setHtmlButtonStatus] = useState("Copy HTML"); - const [markdownButtonStatus, setMarkdownButtonStatus] = - useState("Copy markdown"); - let handleHtmlButton = () => { - setHtmlButtonStatus("Copied"); - let newtimeoutId = setTimeout(async () => { - setHtmlButtonStatus("Copy HTML"); - }, 2000); - }; - let handleMarkdownButton = () => { - setMarkdownButtonStatus("Copied"); - let newtimeoutId = setTimeout(async () => { - setMarkdownButtonStatus("Copy markdown"); - }, 2000); - }; - - if (result && imgSrc && hasDisplayBeenCaptured) { - return ( -
-

- {generateMarkdown(result, imgSrc)} -

- handleMarkdownButton()} - > - - -

- {generateHtml(result, imgSrc)} -

- handleHtmlButton()} - > - - -
- ); - } else { - return null; - } -}; - -let generateIframeURL = (result) => { - let iframeURL = ""; - if (result) { - // if check not strictly necessary today - let parts = result.url.replace("questions", "questions/embed").split("/"); - parts.pop(); - parts.pop(); - iframeURL = parts.join("/"); - } - return iframeURL; -}; - -let metaculusEmbed = (result) => { - let platform = ""; - let iframeURL = ""; - if (result) { - iframeURL = generateIframeURL(result); - platform = result.platform; - } - - return ( -