diff --git a/i18n/en/code.json b/i18n/en/code.json index faa3f627d92b..0c4c9768ea00 100644 --- a/i18n/en/code.json +++ b/i18n/en/code.json @@ -628,5 +628,9 @@ "theme.blog.author.noPosts": { "message": "This author has not written any posts yet.", "description": "The text for authors with 0 blog post" + }, + "theme.feedback.helpful": { + "message": "Is this page helpful?", + "description": "The text asking if the page is helpful" } } diff --git a/package.json b/package.json index 3062d39a58fa..87f462e1aa30 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@docusaurus/preset-classic": "^3.6.3", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@feelback/react": "^0.3.4", "clsx": "^1.2.1", "docusaurus-plugin-openapi-docs": "^4.0.1", "docusaurus-theme-openapi-docs": "^4.0.1", diff --git a/src/theme/Footer/Feedback/Feedback.js b/src/theme/Footer/Feedback/Feedback.js new file mode 100644 index 000000000000..2cbb2ee3dec8 --- /dev/null +++ b/src/theme/Footer/Feedback/Feedback.js @@ -0,0 +1,105 @@ +// /src/theme/Footer/Feedback/Feedback.js +import React, { useEffect, useState } from 'react'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { FeedbackButton } from 'pushfeedback-react'; +import { defineCustomElements } from 'pushfeedback/loader'; +import 'pushfeedback/dist/pushfeedback/pushfeedback.css'; +import styles from './Feedback.module.css'; + +const placeholders = { + 'en': { + feedbackButtonText: "Make this page better", + modalTitle: "Your feedback helps us improve.", + emailPlaceholder: "Enter your email", + errorMessage: "Please try again later.", + modalTitleError403: "The request URL does not match the one defined in PushFeedback for this project.", + modalTitleError404: "We could not find the provided project id in PushFeedback.", + messagePlaceholder: "Comments", + modalTitleError: "Oops!", + modalTitleSuccess: "Thanks for your feedback!", + screenshotButtonText: "Add a Screenshot", + screenshotTopbarText: "SELECT AN ELEMENT ON THE PAGE", + sendButtonText: "Send", + ratingPlaceholder: "Was this page helpful?", + ratingStarsPlaceholder: "How would you rate this page?" + }, + 'ko': { + feedbackButtonText: "페이지를 개선해 주세요", + modalTitle: "여러분의 피드백은 문서 개선에 큰 도움이 됩니다.", + emailPlaceholder: "이메일을 입력해 주세요", + errorMessage: "나중에 다시 시도해주세요.", + modalTitleError403: "요청 URL이 PushFeedback에서 정의된 URL과 일치하지 않습니다.", + modalTitleError404: "PushFeedback에서 제공된 프로젝트 ID를 찾을 수 없습니다.", + messagePlaceholder: "의견", + modalTitleError: "이런!", + modalTitleSuccess: "피드백을 보내주셔서 감사합니다!", + screenshotButtonText: "스크린샷 찍기", + screenshotTopbarText: "해당 위치를 선택하세요", + sendButtonText: "보내기", + ratingPlaceholder: "이 페이지가 도움이 되었나요?", + ratingStarsPlaceholder: "이 페이지를 평가해 주세요." + }, + 'zh-CN': { + feedbackButtonText: "让这个页面变得更好", + modalTitle: "您的反馈有助于我们改进工作。", + emailPlaceholder: "请输入您的电子邮件", + errorMessage: "请稍后再试", + modalTitleError403: "请求的 URL 与 PushFeedback 中为该项目定义的 URL 不匹配。", + modalTitleError404: "我们无法在 PushFeedback 中找到所提供的项目 ID。", + messagePlaceholder: "评论", + modalTitleError: "哎呀!", + modalTitleSuccess: "感谢您的反馈!", + screenshotButtonText: "添加截图", + screenshotTopbarText: "在页面上选择一个元素", + sendButtonText: "发送", + ratingPlaceholder: "本页对您有帮助吗?", + ratingStarsPlaceholder: "您如何评价本页面?" + }, + 'vi': { + feedbackButtonText: "Cải thiện trang này", + modalTitle: "Phản hồi của bạn giúp chúng tôi cải thiện.", + emailPlaceholder: "Nhập email của bạn", + errorMessage: "Vui lòng thử lại sau.", + modalTitleError403: "URL yêu cầu không khớp với URL được xác định trong PushFeedback cho dự án này.", + modalTitleError404: "Chúng tôi không thể tìm thấy ID dự án được cung cấp trong PushFeedback.", + messagePlaceholder: "Nhận xét", + modalTitleError: "Ôi!", + modalTitleSuccess: "Cảm ơn bạn đã phản hồi!", + screenshotButtonText: "Chụp ảnh màn hình", + screenshotTopbarText: "CHỌN MỘT YẾU TỐ TRÊN TRANG", + sendButtonText: "Gửi", + ratingPlaceholder: "Trang này có hữu ích không?", + ratingStarsPlaceholder: "Bạn đánh giá trang này như thế nào" + } +}; + +export default function PushFeedback() { + const { i18n } = useDocusaurusContext(); + const language = i18n.currentLocale; + const [isLoaded, setIsLoaded] = useState(false); + const projectId = '8ou0itrmqd'; + +useEffect(() => { + if (typeof window !== 'undefined') { + defineCustomElements(window); + setIsLoaded(true); // Set loaded immediately after defining elements + } +}, []); + + const texts = placeholders[language] || placeholders.en; + + return ( +
+ + {texts.feedbackButtonText} + +
+ ); +} \ No newline at end of file diff --git a/src/theme/Footer/Feedback/Feedback.module.css b/src/theme/Footer/Feedback/Feedback.module.css new file mode 100644 index 000000000000..4df32c2cc54d --- /dev/null +++ b/src/theme/Footer/Feedback/Feedback.module.css @@ -0,0 +1,20 @@ +/* /src/theme/Footer/Feedback/Feedback.module.css */ +.feedbackWrapper { + opacity: 0; + transition: opacity 0.3s ease-in; + pointer-events: none; +} + +.loaded { + opacity: 1; + pointer-events: auto; +} + +.feedbackWrapper:not(.loaded) :global(pushfeedback-button) { + display: none; +} + +:global(:root) { + --feedback-primary-color: #789806; + --feedback-button-dark-bg-color: var(--feedback-primary-color); +} \ No newline at end of file diff --git a/src/theme/Footer/Rating/Rating.js b/src/theme/Footer/Rating/Rating.js new file mode 100644 index 000000000000..a0e6d73816ee --- /dev/null +++ b/src/theme/Footer/Rating/Rating.js @@ -0,0 +1,78 @@ +import React, { useEffect, useRef } from 'react'; +import ReactDOM from 'react-dom/client'; +import { FeelbackYesNo } from "@feelback/react"; +import { useLocation } from '@docusaurus/router'; +import Translate from '@docusaurus/Translate'; +import styles from './Rating.module.css'; +import ThumbsUp from '@site/static/img/misc/button-thumbs-up.svg'; +import ThumbsDown from '@site/static/img/misc/button-thumbs-down.svg'; + +const CUSTOM_LIKE_DISLIKE = [ + { + value: "+1", + icon: , + title: "Like", + }, + { + value: "-1", + icon: , + title: "Dislike", + } +]; + +export default function FeelbackRating() { + const rootRef = useRef(null); + const location = useLocation(); + const containerRef = useRef(null); + + useEffect(() => { + if (typeof window === 'undefined' || location.pathname === '/') { + return; + } + + if (!containerRef.current) { + containerRef.current = document.createElement('div'); + containerRef.current.className = styles.feedbackContainer; + } + + if (!rootRef.current) { + const footerElement = document.querySelector('footer'); + if (footerElement) { + footerElement.appendChild(containerRef.current); + rootRef.current = ReactDOM.createRoot(containerRef.current); + + rootRef.current.render( + <> +
+
+ + + Is this page helpful? + + + +
+ + ); + } + } + + return () => { + if (rootRef.current) { + rootRef.current.unmount(); + containerRef.current?.remove(); + rootRef.current = null; + containerRef.current = null; + } + }; + }, [location.pathname]); + + return null; +} \ No newline at end of file diff --git a/src/theme/Footer/Rating/Rating.module.css b/src/theme/Footer/Rating/Rating.module.css new file mode 100644 index 000000000000..97126eb58283 --- /dev/null +++ b/src/theme/Footer/Rating/Rating.module.css @@ -0,0 +1,79 @@ +/* /src/theme/Footer/Rating/Rating.module.css */ +.feedbackContainer { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.feedbackDivider { + width: 100%; + border: 0; + height: 1px; + background-color: var(--ifm-color-emphasis-200); + margin: 1rem 0; +} + +.feedbackContent { + display: flex; + align-items: center; + gap: 1rem; +} + +.questionText { + color: var(--ifm-color-emphasis-700); +} + +.feelbackButtonsCustom :global(.feelback-buttons) { + display: flex; + gap: 0.5rem; +} + +.feelbackButtonsCustom :global(.feelback-btn) { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem 1rem; + border: 1px solid var(--ifm-color-primary); + border-radius: 0.375rem; + background-color: transparent; + color: var(--ifm-color-primary); + cursor: pointer; + transition: all 0.2s ease; +} + +.feelbackButtonsCustom :global(.feelback-btn:hover), +.feelbackButtonsCustom :global(.feelback-btn.active) { + background-color: var(--ifm-color-primary); + color: white; +} + +/* Updated SVG styling */ +.feelbackButtonsCustom :global(.feelback-btn) svg { + width: 1.25rem; + height: 1.25rem; + transition: transform 0.2s ease; +} + +.feelbackButtonsCustom :global(.feelback-btn) svg path { + fill: none; + stroke: currentColor; + stroke-width: 1.5px; + transition: stroke-width 0.2s ease; +} + +/* Modified hover selectors */ +.feelbackButtonsCustom :global(.feelback-btn:hover) svg { + transform: scale(1.1); +} + +.feelbackButtonsCustom :global(.feelback-btn:hover) svg path { + stroke-width: 2px; +} + +@media (max-width: 768px) { + .feedbackContent { + flex-direction: column; + text-align: center; + } +} \ No newline at end of file diff --git a/src/theme/Footer/custom.feedback.css b/src/theme/Footer/custom.feedback.css deleted file mode 100644 index 66a9725d4660..000000000000 --- a/src/theme/Footer/custom.feedback.css +++ /dev/null @@ -1,17 +0,0 @@ -:root { - --feedback-primary-color: #789806; - --feedback-button-dark-bg-color: var(--feedback-primary-color); -} - -.feedback-wrapper { - visibility: hidden; -} - -.feedback-wrapper.loaded { - visibility: visible; -} - -/* Hide the feedback button initially */ -.feedback-wrapper:not(.loaded) pushfeedback-button { - display: none; -} \ No newline at end of file diff --git a/src/theme/Footer/index.js b/src/theme/Footer/index.js index ad4bbb53c71b..3c5c6ba6ce09 100644 --- a/src/theme/Footer/index.js +++ b/src/theme/Footer/index.js @@ -1,143 +1,15 @@ -import React, { useEffect, useState } from 'react'; -import Footer from '@theme-original/Footer'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; - -import { FeedbackButton } from 'pushfeedback-react'; -import { defineCustomElements } from 'pushfeedback/loader'; -import 'pushfeedback/dist/pushfeedback/pushfeedback.css'; -import './custom.feedback.css'; +// /src/theme/Footer/index.js +import React from 'react'; +import OriginalFooter from '@theme-original/Footer'; +import PushFeedback from './Feedback/Feedback'; +import FeelbackRating from './Rating/Rating'; export default function FooterWrapper(props) { - const { i18n } = useDocusaurusContext(); - const language = i18n.currentLocale; - const [isLoaded, setIsLoaded] = useState(false); - - const projectId = '8ou0itrmqd'; - - const placeholders = { - 'en': { - feedbackButtonText: "Make this page better", - modalTitle: "Your feedback makes a difference. Let us know how we can do better.", - emailPlaceholder: "Enter your email", - errorMessage: "Please try again later.", - modalTitleError403: "The request URL does not match the one defined in PushFeedback for this project.", - modalTitleError404: "We could not find the provided project id in PushFeedback.", - messagePlaceholder: "Comments", - modalTitleError: "Oops!", - modalTitleSuccess: "Thanks for your feedback!", - screenshotButtonText: "Add a Screenshot", - screenshotTopbarText: "SELECT AN ELEMENT ON THE PAGE", - sendButtonText: "Send", - ratingPlaceholder: "Was this page helpful?", - ratingStarsPlaceholder: "How would you rate this page" - }, - 'ko': { - feedbackButtonText: "페이지를 개선해 주세요", - modalTitle: "여러분의 피드백은 소중합니다. 개선할 부분을 알려주세요.", - emailPlaceholder: "이메일을 입력해 주세요", - errorMessage: "나중에 다시 시도해주세요.", - modalTitleError403: "요청 URL이 PushFeedback에서 정의된 URL과 일치하지 않습니다.", - modalTitleError404: "PushFeedback에서 제공된 프로젝트 ID를 찾을 수 없습니다.", - messagePlaceholder: "의견", - modalTitleError: "이런!", - modalTitleSuccess: "피드백을 보내주셔서 감사합니다!", - screenshotButtonText: "스크린샷 찍기", - screenshotTopbarText: "해당 위치를 선택하세요", - sendButtonText: "보내기", - ratingPlaceholder: "이 페이지가 도움이 되었나요?", - ratingStarsPlaceholder: "이 페이지를 평가해 주세요" - }, - 'zh-CN': { - feedbackButtonText: "让这个页面变得更好", - modalTitle: "您的反馈使我们与众不同。让我们知道如何做得更好", - emailPlaceholder: "输入您的电子邮件", - errorMessage: "请稍后再试", - modalTitleError403: "请求的 URL 与 PushFeedback 中为该项目定义的 URL 不匹配", - modalTitleError404: "我们无法在 PushFeedback 中找到所提供的项目 ID。", - messagePlaceholder: "评论", - modalTitleError: "哎呀!", - modalTitleSuccess: "感谢您的反馈!", - screenshotButtonText: "添加截图", - screenshotTopbarText: "在页面上选择一个元素", - sendButtonText: "发送", - ratingPlaceholder: "本页对您有帮助吗?", - ratingStarsPlaceholder: "您如何评价本页面?" - }, - 'vi': { - feedbackButtonText: "Cải thiện trang này", - modalTitle: "Hãy cho chúng tôi biết cách chúng tôi có thể làm tốt hơn.", - emailPlaceholder: "Nhập email của bạn", - errorMessage: "Vui lòng thử lại sau.", - modalTitleError403: "URL yêu cầu không khớp với URL được xác định trong PushFeedback cho dự án này.", - modalTitleError404: "Chúng tôi không thể tìm thấy ID dự án được cung cấp trong PushFeedback.", - messagePlaceholder: "Nhận xét", - modalTitleError: "Ôi!", - modalTitleSuccess: "Cảm ơn bạn đã phản hồi!", - screenshotButtonText: "Chụp ảnh màn hình", - screenshotTopbarText: "CHỌN MỘT YẾU TỐ TRÊN TRANG", - sendButtonText: "Gửi", - ratingPlaceholder: "Trang này có hữu ích không?", - ratingStarsPlaceholder: "Bạn đánh giá trang này như thế nào" - } - }; - - useEffect(() => { - if (typeof window !== 'undefined') { - defineCustomElements(window); - } - - // Use the same timing as the main content load - const timer = setTimeout(() => { - setIsLoaded(true); - }, 100); - - return () => clearTimeout(timer); - }, []); - - const { - feedbackButtonText, - modalTitle, - emailPlaceholder, - errorMessage, - modalTitleError403, - modalTitleError404, - messagePlaceholder, - modalTitleError, - modalTitleSuccess, - screenshotButtonText, - screenshotTopbarText, - sendButtonText, - ratingPlaceholder, - ratingStarsPlaceholder - } = placeholders[language] || placeholders.en; - return ( <> -
- - {feedbackButtonText} - -
-