diff --git a/package.json b/package.json index e21c962a..e2f750bd 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,12 @@ "@primer/octicons-react": "^19.9.0", "@radix-ui/react-dropdown-menu": "^2.1.2", "@reduxjs/toolkit": "^2.2.3", + "@tiptap/extension-bold": "^3.6.2", + "@tiptap/extension-italic": "^3.6.2", + "@tiptap/extension-link": "^3.6.2", + "@tiptap/extension-underline": "^3.6.2", + "@tiptap/react": "^3.6.2", + "@tiptap/starter-kit": "^3.6.2", "auto-launch": "^5.0.6", "axios": "^1.7.9", "axios-cookiejar-support": "^5.0.5", @@ -63,6 +69,7 @@ "jsdom": "^24.0.0", "jsonwebtoken": "^9.0.2", "lodash-es": "^4.17.21", + "lucide-react": "^0.544.0", "parse-torrent": "^11.0.18", "rc-virtual-list": "^3.18.3", "react-dnd": "^16.0.1", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bcf8cf54..b0fee465 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -198,6 +198,29 @@ "hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util it's completed. If Hydra closes before completing, you will lose your progress.", "achievements": "Achievements", "achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Show more", + "show_less": "Show less", + "reviews": "Reviews", + "leave_a_review": "Leave a Review", + "write_review_placeholder": "Share your thoughts about this game...", + "sort_newest": "Newest", + "sort_by": "Sort by", + "no_reviews_yet": "No reviews yet", + "be_first_to_review": "Be the first to share your thoughts about this game!", + "sort_oldest": "Oldest", + "sort_highest_score": "Highest Score", + "sort_lowest_score": "Lowest Score", + "sort_most_voted": "Most Voted", + "rating": "Rating", + "submit_review": "Submit Review", + "submitting": "Submitting...", + "loading_reviews": "Loading reviews...", + "loading_more_reviews": "Loading more reviews...", + "load_more_reviews": "Load More Reviews", + "youve_played_for_hours": "You've played for {{hours}} hours", + "would_you_recommend_this_game": "Would you like to leave a review to this game?", + "yes": "Yes", + "maybe_later": "Maybe Later", "cloud_save": "Cloud save", "cloud_save_description": "Save your progress in the cloud and continue playing on any device", "backups": "Backups", diff --git a/src/main/events/catalogue/check-game-review.ts b/src/main/events/catalogue/check-game-review.ts new file mode 100644 index 00000000..c46ede07 --- /dev/null +++ b/src/main/events/catalogue/check-game-review.ts @@ -0,0 +1,17 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const checkGameReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string +) => { + return HydraApi.get( + `/games/${shop}/${objectId}/reviews/check`, + null, + { needsAuth: true } + ); +}; + +registerEvent("checkGameReview", checkGameReview); \ No newline at end of file diff --git a/src/main/events/catalogue/create-game-review.ts b/src/main/events/catalogue/create-game-review.ts new file mode 100644 index 00000000..7f29b639 --- /dev/null +++ b/src/main/events/catalogue/create-game-review.ts @@ -0,0 +1,18 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const createGameReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + reviewHtml: string, + score: number +) => { + return HydraApi.post(`/games/${shop}/${objectId}/reviews`, { + reviewHtml, + score, + }); +}; + +registerEvent("createGameReview", createGameReview); \ No newline at end of file diff --git a/src/main/events/catalogue/delete-review.ts b/src/main/events/catalogue/delete-review.ts new file mode 100644 index 00000000..2048b3e7 --- /dev/null +++ b/src/main/events/catalogue/delete-review.ts @@ -0,0 +1,14 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const deleteReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + reviewId: string +) => { + return HydraApi.delete(`/games/${shop}/${objectId}/reviews/${reviewId}`); +}; + +registerEvent("deleteReview", deleteReview); \ No newline at end of file diff --git a/src/main/events/catalogue/get-game-reviews.ts b/src/main/events/catalogue/get-game-reviews.ts new file mode 100644 index 00000000..d3c31780 --- /dev/null +++ b/src/main/events/catalogue/get-game-reviews.ts @@ -0,0 +1,26 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const getGameReviews = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + take: number = 20, + skip: number = 0, + sortBy: string = "newest" +) => { + const params = new URLSearchParams({ + take: take.toString(), + skip: skip.toString(), + sortBy, + }); + + return HydraApi.get( + `/games/${shop}/${objectId}/reviews?${params.toString()}`, + null, + { needsAuth: false } + ); +}; + +registerEvent("getGameReviews", getGameReviews); \ No newline at end of file diff --git a/src/main/events/catalogue/vote-review.ts b/src/main/events/catalogue/vote-review.ts new file mode 100644 index 00000000..b60062c3 --- /dev/null +++ b/src/main/events/catalogue/vote-review.ts @@ -0,0 +1,15 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { GameShop } from "@types"; + +const voteReview = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + reviewId: string, + voteType: 'upvote' | 'downvote' +) => { + return HydraApi.put(`/games/${shop}/${objectId}/reviews/${reviewId}/${voteType}`, {}); +}; + +registerEvent("voteReview", voteReview); \ No newline at end of file diff --git a/src/main/events/index.ts b/src/main/events/index.ts index d4c461f8..378a3b6e 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -11,6 +11,11 @@ import "./catalogue/get-game-stats"; import "./catalogue/get-trending-games"; import "./catalogue/get-publishers"; import "./catalogue/get-developers"; +import "./catalogue/create-game-review"; +import "./catalogue/get-game-reviews"; +import "./catalogue/vote-review"; +import "./catalogue/delete-review"; +import "./catalogue/check-game-review"; import "./hardware/get-disk-free-space"; import "./hardware/check-folder-write-permission"; import "./library/add-game-to-library"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 17c1225f..eda43369 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -77,6 +77,32 @@ contextBridge.exposeInMainWorld("electron", { getGameStats: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameStats", objectId, shop), getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), + createGameReview: ( + shop: GameShop, + objectId: string, + reviewHtml: string, + score: number + ) => ipcRenderer.invoke("createGameReview", shop, objectId, reviewHtml, score), + getGameReviews: ( + shop: GameShop, + objectId: string, + take?: number, + skip?: number, + sortBy?: string + ) => ipcRenderer.invoke("getGameReviews", shop, objectId, take, skip, sortBy), + voteReview: ( + shop: GameShop, + objectId: string, + reviewId: string, + voteType: "upvote" | "downvote" + ) => ipcRenderer.invoke("voteReview", shop, objectId, reviewId, voteType), + deleteReview: ( + shop: GameShop, + objectId: string, + reviewId: string + ) => ipcRenderer.invoke("deleteReview", shop, objectId, reviewId), + checkGameReview: (shop: GameShop, objectId: string) => + ipcRenderer.invoke("checkGameReview", shop, objectId), onUpdateAchievements: ( objectId: string, shop: GameShop, diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 3cdefc19..cb9a060c 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -1,4 +1,4 @@ -import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; +import { DownloadIcon, PeopleIcon, StarIcon } from "@primer/octicons-react"; import type { GameStats } from "@types"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; @@ -107,6 +107,14 @@ export function GameCard({ game, ...props }: GameCardProps) { {stats ? numberFormatter.format(stats.playerCount) : "…"} + {stats?.averageScore && ( +
+ + + {stats.averageScore.toFixed(1)} + +
+ )} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e6277888..752a1115 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -93,6 +93,34 @@ declare global { ) => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; getTrendingGames: () => Promise; + createGameReview: ( + shop: GameShop, + objectId: string, + reviewHtml: string, + score: number + ) => Promise; + getGameReviews: ( + shop: GameShop, + objectId: string, + take?: number, + skip?: number, + sortBy?: string + ) => Promise; + voteReview: ( + shop: GameShop, + objectId: string, + reviewId: string, + voteType: 'upvote' | 'downvote' + ) => Promise; + deleteReview: ( + shop: GameShop, + objectId: string, + reviewId: string + ) => Promise; + checkGameReview: ( + shop: GameShop, + objectId: string + ) => Promise<{ hasReviewed: boolean }>; onUpdateAchievements: ( objectId: string, shop: GameShop, diff --git a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss index 9483b50e..d1ae2481 100644 --- a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss +++ b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss @@ -65,7 +65,7 @@ &__preview { width: 100%; padding: globals.$spacing-unit 0; - height: 100%; + height: auto; display: flex; position: relative; overflow-x: auto; diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index ca2ca023..48228e8e 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -1,33 +1,45 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { PencilIcon } from "@primer/octicons-react"; +import { PencilIcon, TrashIcon, ClockIcon } from "@primer/octicons-react"; +import { ThumbsUp, ThumbsDown } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { useEditor, EditorContent } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import Bold from '@tiptap/extension-bold'; +import Italic from '@tiptap/extension-italic'; +import Underline from '@tiptap/extension-underline'; +import type { GameReview } from "@types"; import { HeroPanel } from "./hero"; import { DescriptionHeader } from "./description-header/description-header"; import { GallerySlider } from "./gallery-slider/gallery-slider"; import { Sidebar } from "./sidebar/sidebar"; import { EditGameModal } from "./modals"; +import { ReviewSortOptions } from "./review-sort-options"; +import { ReviewPromptBanner } from "./review-prompt-banner"; import { useTranslation } from "react-i18next"; import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; import { AuthPage } from "@shared"; import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif"; -import { useUserDetails, useLibrary } from "@renderer/hooks"; +import { useUserDetails, useLibrary, useDate } from "@renderer/hooks"; import { useSubscription } from "@renderer/hooks/use-subscription"; import "./game-details.scss"; export function GameDetailsContent() { const heroRef = useRef(null); + const navigate = useNavigate(); const { t } = useTranslation("game_details"); - const { objectId, shopDetails, game, hasNSFWContentBlocked, updateGame } = + const { objectId, shopDetails, game, hasNSFWContentBlocked, updateGame, shop } = useContext(gameDetailsContext); const { showHydraCloudModal } = useSubscription(); const { userDetails, hasActiveSubscription } = useUserDetails(); const { updateLibrary } = useLibrary(); + const { formatDistance } = useDate(); const { setShowCloudSyncModal, getGameArtifacts } = useContext(cloudSyncContext); @@ -80,6 +92,41 @@ export function GameDetailsContent() { const [backdropOpacity, setBackdropOpacity] = useState(1); const [showEditGameModal, setShowEditGameModal] = useState(false); + const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); + + // Reviews state management + const [reviews, setReviews] = useState([]); + const [reviewsLoading, setReviewsLoading] = useState(false); + const [reviewScore, setReviewScore] = useState(5); + const [submittingReview, setSubmittingReview] = useState(false); + const [reviewsSortBy, setReviewsSortBy] = useState("newest"); + const [reviewsPage, setReviewsPage] = useState(0); + const [hasMoreReviews, setHasMoreReviews] = useState(true); + const [visibleBlockedReviews, setVisibleBlockedReviews] = useState>(new Set()); + const [totalReviewCount, setTotalReviewCount] = useState(0); + const [showReviewForm, setShowReviewForm] = useState(false); + + // Review prompt banner state + const [showReviewPrompt, setShowReviewPrompt] = useState(false); + const [hasUserReviewed, setHasUserReviewed] = useState(false); + const [reviewCheckLoading, setReviewCheckLoading] = useState(false); + + // Tiptap editor for review input + const editor = useEditor({ + extensions: [ + StarterKit, + Bold, + Italic, + Underline, + ], + content: '', + editorProps: { + attributes: { + class: 'game-details__review-editor', + 'data-placeholder': t("write_review_placeholder"), + }, + }, + }); useEffect(() => { setBackdropOpacity(1); @@ -114,6 +161,188 @@ export function GameDetailsContent() { const isCustomGame = game?.shop === "custom"; + // Reviews functions + const checkUserReview = async () => { + if (!objectId || !userDetails) return; + + setReviewCheckLoading(true); + try { + const response = await window.electron.checkGameReview(shop, objectId); + const hasReviewed = (response as any)?.hasReviewed || false; + setHasUserReviewed(hasReviewed); + + // Show prompt only if user hasn't reviewed and has played the game + if (!hasReviewed && game?.playTimeInMilliseconds && game.playTimeInMilliseconds > 0) { + setShowReviewPrompt(true); + } + } catch (error) { + console.error("Failed to check user review:", error); + } finally { + setReviewCheckLoading(false); + } + }; + + const loadReviews = async (reset = false) => { + if (!objectId) return; + + setReviewsLoading(true); + try { + const skip = reset ? 0 : reviewsPage * 20; + const response = await window.electron.getGameReviews( + shop, + objectId, + 20, + skip, + reviewsSortBy + ); + + // Handle the response structure: { totalCount: number, reviews: Review[] } + const reviewsData = (response as any)?.reviews || []; + const reviewCount = (response as any)?.totalCount || 0; + + if (reset) { + setReviews(reviewsData); + setReviewsPage(0); + setTotalReviewCount(reviewCount); + } else { + setReviews(prev => [...prev, ...reviewsData]); + } + + setHasMoreReviews(reviewsData.length === 20); + } catch (error) { + console.error("Failed to load reviews:", error); + } finally { + setReviewsLoading(false); + } + }; + + const handleVoteReview = async (reviewId: string, voteType: 'upvote' | 'downvote') => { + if (!objectId) return; + + try { + await window.electron.voteReview(shop, objectId, reviewId, voteType); + // Reload reviews to get updated vote counts + loadReviews(true); + } catch (error) { + console.error(`Failed to ${voteType} review:`, error); + } + }; + + const handleDeleteReview = async (reviewId: string) => { + if (!objectId) return; + + try { + await window.electron.deleteReview(shop, objectId, reviewId); + // Reload reviews after deletion + loadReviews(true); + } catch (error) { + console.error('Failed to delete review:', error); + } + }; + + const handleSubmitReview = async () => { + console.log("handleSubmitReview called"); + console.log("game:", game); + console.log("objectId:", objectId); + + const reviewHtml = editor?.getHTML() || ''; + console.log("reviewHtml:", reviewHtml); + console.log("reviewScore:", reviewScore); + console.log("submittingReview:", submittingReview); + + if (!objectId || !reviewHtml.trim() || submittingReview) { + console.log("Early return - validation failed"); + return; + } + + console.log("Starting review submission..."); + setSubmittingReview(true); + try { + console.log("Calling window.electron.createGameReview..."); + await window.electron.createGameReview( + shop, + objectId, + reviewHtml, + reviewScore + ); + + console.log("Review submitted successfully"); + editor?.commands.clearContent(); + setReviewScore(5); + await loadReviews(true); // Reload reviews after submission + setShowReviewForm(false); // Hide the review form after successful submission + setShowReviewPrompt(false); // Hide the prompt banner + setHasUserReviewed(true); // Update the review status + } catch (error) { + console.error("Failed to submit review:", error); + } finally { + setSubmittingReview(false); + console.log("Review submission completed"); + } + }; + + // Review prompt banner handlers + const handleReviewPromptYes = () => { + setShowReviewPrompt(false); + setShowReviewForm(true); + + // Scroll to review form + setTimeout(() => { + const reviewFormElement = document.querySelector('.game-details__review-form'); + if (reviewFormElement) { + reviewFormElement.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }, 100); + }; + + const handleReviewPromptLater = () => { + setShowReviewPrompt(false); + }; + + const handleSortChange = (newSortBy: string) => { + setReviewsSortBy(newSortBy); + setReviewsPage(0); + setHasMoreReviews(true); + loadReviews(true); + }; + + const toggleBlockedReview = (reviewId: string) => { + setVisibleBlockedReviews(prev => { + const newSet = new Set(prev); + if (newSet.has(reviewId)) { + newSet.delete(reviewId); + } else { + newSet.add(reviewId); + } + return newSet; + }); + }; + + const loadMoreReviews = () => { + if (!reviewsLoading && hasMoreReviews) { + setReviewsPage(prev => prev + 1); + loadReviews(false); + } + }; + + // Load reviews when component mounts or sort changes + useEffect(() => { + if (objectId && (game || shop)) { + loadReviews(true); + checkUserReview(); // Check if user has reviewed this game + } + }, [game, shop, objectId, reviewsSortBy, userDetails]); + + // Load more reviews when page changes + useEffect(() => { + if (reviewsPage > 0) { + loadReviews(false); + } + }, [reviewsPage]); + // Helper function to get image with custom asset priority const getImageWithCustomPriority = ( customUrl: string | null | undefined, @@ -227,6 +456,14 @@ export function GameDetailsContent() {
+ {/* Review Prompt Banner */} + {showReviewPrompt && userDetails && game?.playTimeInMilliseconds && !hasUserReviewed && !reviewCheckLoading && ( + + )} + @@ -234,8 +471,237 @@ export function GameDetailsContent() { dangerouslySetInnerHTML={{ __html: aboutTheGame, }} - className="game-details__description" + className={`game-details__description ${ + isDescriptionExpanded ? 'game-details__description--expanded' : 'game-details__description--collapsed' + }`} /> + + {aboutTheGame && aboutTheGame.length > 500 && ( + + )} + +
+ {showReviewForm && ( + <> +
+

{t("leave_a_review")}

+
+ +
+
+ +
+
+ + + +
+ + +
+
+ +
+
+ + +
+
+
+ + )} + + {showReviewForm && ( +
+ )} + +
+
+
+

+ {t("reviews")} +

+ + {totalReviewCount} + +
+ +
+ + {reviewsLoading && reviews.length === 0 && ( +
+ {t("loading_reviews")} +
+ )} + + {!reviewsLoading && reviews.length === 0 && ( +
+
📝
+

+ {t("no_reviews_yet")} +

+

+ {t("be_first_to_review")} +

+
+ )} + + {reviews.map((review, index) => ( +
+ {review.isBlocked && !visibleBlockedReviews.has(review.id) ? ( +
+ Review from blocked user — + +
+ ) : ( + <> +
+
+ {review.user?.profileImageUrl && ( + {review.user.displayName + )} +
+
review.user?.id && navigate(`/profile/${review.user.id}`)} + > + {review.user?.displayName || 'Anonymous'} +
+
+ + {formatDistance(new Date(review.createdAt), new Date(), { addSuffix: true })} +
+
+
+
+ {review.score}/10 +
+
+
+
+
+ + +
+ {userDetails?.id === review.user?.id && ( + + )} + {review.isBlocked && visibleBlockedReviews.has(review.id) && ( + + )} +
+ + )} +
+ ))} + + {hasMoreReviews && !reviewsLoading && ( + + )} + + {reviewsLoading && reviews.length > 0 && ( +
+ {t("loading_more_reviews")} +
+ )} +
+
{game?.shop !== "custom" && } diff --git a/src/renderer/src/pages/game-details/game-details-skeleton.tsx b/src/renderer/src/pages/game-details/game-details-skeleton.tsx index c39da3bb..adaf4ab2 100644 --- a/src/renderer/src/pages/game-details/game-details-skeleton.tsx +++ b/src/renderer/src/pages/game-details/game-details-skeleton.tsx @@ -35,6 +35,7 @@ export function GameDetailsSkeleton() { ))} +
diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index e1140d31..b82bd6b1 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -27,6 +27,418 @@ $hero-height: 300px; } } + &__review-form { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + margin-bottom: calc(globals.$spacing-unit * 3); + padding: calc(globals.$spacing-unit * 2); + background: rgba(255, 255, 255, 0.02); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.05); + } + + &__review-form-controls { + display: flex; + gap: calc(globals.$spacing-unit * 2); + align-items: flex-end; + flex-wrap: wrap; + + @media (max-width: 768px) { + flex-direction: column; + align-items: stretch; + gap: calc(globals.$spacing-unit * 1.5); + } + } + + &__review-form-bottom { + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: calc(globals.$spacing-unit * 2); + + @media (max-width: 768px) { + flex-direction: column; + align-items: stretch; + gap: calc(globals.$spacing-unit * 1.5); + } + } + + &__review-score-container { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.75); + min-width: 120px; + } + + &__review-score-label { + display: block; + font-size: globals.$body-font-size; + color: globals.$body-color; + } + + &__review-score-select { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + border-radius: 4px; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1); + color: globals.$body-color; + font-size: globals.$body-font-size; + font-family: inherit; + cursor: pointer; + transition: border-color 0.2s ease, background-color 0.2s ease; + + &:focus { + outline: none; + background-color: rgba(255, 255, 255, 0.08); + border-color: globals.$brand-teal; + } + + &:hover { + border-color: rgba(255, 255, 255, 0.15); + } + + option { + background-color: globals.$dark-background-color; + color: globals.$body-color; + } + } + + &__reviews-sort { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.75); + min-width: 150px; + } + + &__reviews-sort-label { + display: block; + font-size: globals.$body-font-size; + color: globals.$body-color; + } + + &__reviews-sort-select { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + border-radius: 4px; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1); + color: globals.$body-color; + font-size: globals.$body-font-size; + font-family: inherit; + cursor: pointer; + transition: border-color 0.2s ease, background-color 0.2s ease; + + &:focus { + outline: none; + background-color: rgba(255, 255, 255, 0.08); + border-color: globals.$brand-teal; + } + + &:hover { + border-color: rgba(255, 255, 255, 0.15); + } + + option { + background-color: globals.$dark-background-color; + color: globals.$body-color; + } + } + + &__review-submit-button { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + color: globals.$body-color; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + border-radius: 6px; + cursor: pointer; + font-size: globals.$small-font-size; + font-family: inherit; + transition: all ease 0.2s; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + white-space: nowrap; + + &:hover:not(:disabled) { + background-color: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.15); + } + + &:active { + opacity: 0.9; + } + + &:disabled { + background: rgba(255, 255, 255, 0.1); + cursor: not-allowed; + color: rgba(255, 255, 255, 0.5); + } + } + + &__reviews-list { + margin-top: calc(globals.$spacing-unit * 3); + } + + &__reviews-separator { + height: 1px; + background: rgba(255, 255, 255, 0.1); + margin: calc(globals.$spacing-unit * 3) 0; + width: 100%; + } + + &__reviews-list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: calc(globals.$spacing-unit * 2); + padding-bottom: calc(globals.$spacing-unit * 1); + } + + &__reviews-empty { + text-align: center; + padding: calc(globals.$spacing-unit * 4) calc(globals.$spacing-unit * 2); + background: rgba(255, 255, 255, 0.02); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 8px; + margin-bottom: calc(globals.$spacing-unit * 2); + } + + &__reviews-empty-icon { + font-size: 48px; + margin-bottom: calc(globals.$spacing-unit * 2); + opacity: 0.6; + } + + &__reviews-empty-title { + color: rgba(255, 255, 255, 0.9); + font-weight: 600; + margin: 0 0 calc(globals.$spacing-unit * 1) 0; + } + + &__reviews-empty-message { + color: rgba(255, 255, 255, 0.6); + font-size: globals.$small-font-size; + margin: 0; + line-height: 1.4; + } + + &__review-item { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 6px; + padding: calc(globals.$spacing-unit * 2); + margin-bottom: calc(globals.$spacing-unit * 2); + } + + &__review-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: calc(globals.$spacing-unit * 1.5); + } + + &__review-user { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 1); + } + + &__review-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; + border: 2px solid rgba(255, 255, 255, 0.1); + } + + &__review-user-info { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.25); + } + + &__review-display-name { + color: rgba(255, 255, 255, 0.9); + font-size: globals.$small-font-size; + font-weight: 600; + + &--clickable { + cursor: pointer; + transition: color 0.2s ease; + + &:hover { + text-decoration: underline; + } + } + } + + &__review-actions { + margin-top: 12px; + padding-top: 8px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + justify-content: space-between; + align-items: center; + } + + &__review-votes { + display: flex; + gap: 12px; + } + + &__vote-button { + display: flex; + align-items: center; + gap: 6px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 6px; + padding: 6px 12px; + color: #ccc; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); + } + + &--upvote:hover { + color: #4caf50; + border-color: #4caf50; + } + + &--downvote:hover { + color: #f44336; + border-color: #f44336; + } + + &--active { + &.game-details__vote-button--upvote { + svg { + fill: white; + } + } + + &.game-details__vote-button--downvote { + svg { + fill: white; + } + } + } + + span { + font-weight: 500; + } + } + + &__delete-review-button { + display: flex; + align-items: center; + justify-content: center; + background: rgba(244, 67, 54, 0.1); + border: 1px solid rgba(244, 67, 54, 0.3); + border-radius: 6px; + padding: 6px; + color: #f44336; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: rgba(244, 67, 54, 0.2); + border-color: #f44336; + color: #ff5722; + } + } + + &__blocked-review-simple { + color: rgba(255, 255, 255, 0.6); + font-size: globals.$small-font-size; + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 0.5); + } + + &__blocked-review-show-link { + background: none; + border: none; + color: #ffc107; + font-size: globals.$small-font-size; + cursor: pointer; + text-decoration: underline; + padding: 0; + transition: color 0.2s ease; + + &:hover { + color: #ffeb3b; + } + } + + &__blocked-review-hide-link { + background: none; + border: none; + color: rgba(255, 255, 255, 0.5); + font-size: globals.$small-font-size; + cursor: pointer; + text-decoration: underline; + padding: 0; + transition: color 0.2s ease; + + &:hover { + color: rgba(255, 255, 255, 0.8); + } + } + + &__review-score { + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.9); + padding: calc(globals.$spacing-unit * 0.5) calc(globals.$spacing-unit * 1); + border-radius: 4px; + font-size: globals.$small-font-size; + font-weight: 600; + border: 1px solid rgba(255, 255, 255, 0.15); + } + + &__review-date { + display: flex; + align-items: center; + gap: 4px; + color: rgba(255, 255, 255, 0.6); + font-size: globals.$small-font-size; + } + + &__review-content { + color: globals.$body-color; + line-height: 1.5; + } + + &__reviews-loading { + text-align: center; + color: rgba(255, 255, 255, 0.6); + padding: calc(globals.$spacing-unit * 2); + } + + &__load-more-reviews { + background: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + color: globals.$body-color; + padding: calc(globals.$spacing-unit * 1) calc(globals.$spacing-unit * 2); + border-radius: 4px; + cursor: pointer; + font-size: globals.$body-font-size; + font-family: inherit; + transition: all 0.2s ease; + width: 100%; + margin-top: calc(globals.$spacing-unit * 2); + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: globals.$brand-teal; + } + } + &__hero { width: 100%; height: $hero-height; @@ -192,6 +604,8 @@ $hero-height: 300px; min-width: 0; flex: 1; overflow-x: hidden; + display: flex; + flex-direction: column; } &__description { @@ -203,6 +617,7 @@ $hero-height: 300px; margin-right: auto; overflow-x: auto; min-height: auto; + transition: max-height 0.3s ease-in-out; @media (min-width: 1280px) { width: 60%; @@ -212,6 +627,27 @@ $hero-height: 300px; width: 50%; } + &--collapsed { + max-height: 300px; + overflow: hidden; + position: relative; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 60px; + background: linear-gradient(transparent, globals.$background-color); + pointer-events: none; + } + } + + &--expanded { + max-height: none; + } + img, video { border-radius: 5px; @@ -237,6 +673,24 @@ $hero-height: 300px; } } + &__description-toggle { + background: none; + border: 1px solid globals.$border-color; + color: globals.$body-color; + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + border-radius: 4px; + cursor: pointer; + font-size: globals.$body-font-size; + margin-top: calc(globals.$spacing-unit * 1.5); + transition: all 0.2s ease; + align-self: center; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: globals.$brand-teal; + } + } + &__description-skeleton { display: flex; flex-direction: column; @@ -367,4 +821,206 @@ $hero-height: 300px; flex: 1; transition: opacity 0.2s ease; } + + &__reviews-section { + margin-top: calc(globals.$spacing-unit * 3); + padding-top: calc(globals.$spacing-unit * 3); + border-top: 1px solid rgba(255, 255, 255, 0.1); + width: 100%; + margin-left: auto; + margin-right: auto; + + @media (min-width: 1280px) { + width: 60%; + } + + @media (min-width: 1536px) { + width: 50%; + } + } + + &__reviews-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: calc(globals.$spacing-unit * 2); + gap: calc(globals.$spacing-unit * 2); + + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + gap: calc(globals.$spacing-unit * 1.5); + } + } + + &__reviews-title { + font-size: 1.25rem; + font-weight: 600; + color: globals.$muted-color; + margin: 0; + } + + &__reviews-title-group { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit); + flex: 1; + } + + &__reviews-badge { + background-color: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.7); + padding: 4px 8px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + min-width: 24px; + text-align: center; + flex-shrink: 0; + } + + &__leave-review-cta { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 0.5); + padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1.5); + background: linear-gradient(135deg, globals.$brand-teal, globals.$brand-blue); + color: white; + border: none; + border-radius: 8px; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + margin-bottom: calc(globals.$spacing-unit); + + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(globals.$brand-teal, 0.3); + } + + &:active { + transform: translateY(0); + } + + svg { + flex-shrink: 0; + } + } + + &__review-input-container { + width: 100%; + position: relative; + } + + &__review-input-bottom { + position: absolute; + bottom: 8px; + left: 8px; + right: 8px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + z-index: 10; + } + + &__review-editor-toolbar { + display: flex; + gap: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + padding: 4px; + backdrop-filter: blur(10px); + background: rgba(0, 0, 0, 0.3); + } + + &__editor-button { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 3px; + color: globals.$body-color; + padding: 4px 6px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: all 0.2s ease; + min-width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + + &:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.2); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &.is-active { + background: globals.$brand-teal; + border-color: globals.$brand-teal; + color: white; + } + } + + &__review-input { + width: 100%; + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid globals.$border-color; + border-radius: 6px; + padding: calc(globals.$spacing-unit * 1.5); + padding-bottom: calc(globals.$spacing-unit * 3.5); + color: globals.$body-color; + font-size: globals.$body-font-size; + font-family: inherit; + line-height: 1.5; + min-height: 100px; + transition: border-color 0.2s ease, background-color 0.2s ease; + + &:focus-within { + outline: none; + background-color: rgba(255, 255, 255, 0.08); + border-color: globals.$brand-teal; + } + + &:hover { + border-color: rgba(255, 255, 255, 0.15); + } + + .ProseMirror { + outline: none; + min-height: 80px; + + &:empty:before { + content: attr(data-placeholder); + color: rgba(208, 209, 215, 0.6); + pointer-events: none; + } + + p { + margin: 0 0 8px 0; + + &:last-child { + margin-bottom: 0; + } + } + + strong { + font-weight: 700; + } + + em { + font-style: italic; + } + + u { + text-decoration: underline; + } + } + } } diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.scss b/src/renderer/src/pages/game-details/review-prompt-banner.scss new file mode 100644 index 00000000..28ba1e47 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-prompt-banner.scss @@ -0,0 +1,46 @@ +@use "../../scss/globals.scss"; + +.review-prompt-banner { + background: rgba(255, 255, 255, 0.02); + border-radius: 8px; + padding: calc(globals.$spacing-unit * 2); + margin-bottom: calc(globals.$spacing-unit * 3); + border: 1px solid rgba(255, 255, 255, 0.05); + + &__content { + display: flex; + align-items: center; + justify-content: space-between; + gap: calc(globals.$spacing-unit * 2.5); + + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + gap: calc(globals.$spacing-unit * 2); + } + } + + &__text { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.5); + } + + &__playtime { + font-size: globals.$body-font-size; + color: globals.$body-color; + font-weight: 600; + } + + &__question { + font-size: globals.$small-font-size; + color: globals.$muted-color; + font-weight: 400; + } + + &__actions { + display: flex; + gap: globals.$spacing-unit; + align-items: center; + } +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/review-prompt-banner.tsx b/src/renderer/src/pages/game-details/review-prompt-banner.tsx new file mode 100644 index 00000000..87c1b170 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-prompt-banner.tsx @@ -0,0 +1,44 @@ +import { useTranslation } from "react-i18next"; +import { Button } from "@renderer/components"; +import "./review-prompt-banner.scss"; + +interface ReviewPromptBannerProps { + onYesClick: () => void; + onLaterClick: () => void; +} + +export function ReviewPromptBanner({ + onYesClick, + onLaterClick, +}: ReviewPromptBannerProps) { + const { t } = useTranslation("game_details"); + + return ( +
+
+
+ + You've seemed to enjoy this game + + + {t("would_you_recommend_this_game")} + +
+
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/review-sort-options.scss b/src/renderer/src/pages/game-details/review-sort-options.scss new file mode 100644 index 00000000..e982cb24 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-sort-options.scss @@ -0,0 +1,72 @@ +@use "../../scss/globals.scss"; + +.review-sort-options { + &__container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: calc(globals.$spacing-unit); + } + + &__label { + color: rgba(255, 255, 255, 0.6); + font-size: 14px; + font-weight: 400; + } + + &__options { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit); + font-size: 14px; + flex-wrap: wrap; + + @media (max-width: 768px) { + gap: calc(globals.$spacing-unit * 0.75); + } + } + + &__option { + background: none; + border: none; + color: rgba(255, 255, 255, 0.4); + cursor: pointer; + padding: 4px 0; + font-size: 14px; + font-weight: 300; + transition: all ease 0.2s; + display: flex; + align-items: center; + gap: 6px; + + &:hover:not(:disabled) { + color: rgba(255, 255, 255, 0.6); + } + + &.active { + color: rgba(255, 255, 255, 0.9); + font-weight: 500; + } + + span { + display: inline-block; + + @media (max-width: 480px) { + display: none; + } + } + + @media (max-width: 480px) { + gap: 0; + } + } + + &__separator { + color: rgba(255, 255, 255, 0.3); + font-size: 14px; + + @media (max-width: 480px) { + display: none; + } + } +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/review-sort-options.tsx b/src/renderer/src/pages/game-details/review-sort-options.tsx new file mode 100644 index 00000000..5ec25c31 --- /dev/null +++ b/src/renderer/src/pages/game-details/review-sort-options.tsx @@ -0,0 +1,60 @@ +import { CalendarIcon, StarIcon, ThumbsupIcon, ClockIcon } from "@primer/octicons-react"; +import { useTranslation } from "react-i18next"; +import "./review-sort-options.scss"; + +type ReviewSortOption = "newest" | "oldest" | "score_high" | "score_low" | "most_voted"; + +interface ReviewSortOptionsProps { + sortBy: ReviewSortOption; + onSortChange: (sortBy: ReviewSortOption) => void; +} + +export function ReviewSortOptions({ sortBy, onSortChange }: ReviewSortOptionsProps) { + const { t } = useTranslation("game_details"); + + return ( +
+
+ + | + + | + + | + + | + +
+
+ ); +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 593c45be..f4b0645b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -234,6 +234,25 @@ export interface GameStats { downloadCount: number; playerCount: number; assets: ShopAssets | null; + averageScore: number | null; +} + +export interface GameReview { + id: string; + reviewHtml: string; + score: number; + createdAt: string; + updatedAt: string; + upvotes: number; + downvotes: number; + isBlocked: boolean; + hasUpvoted: boolean; + hasDownvoted: boolean; + user: { + id: string; + displayName: string; + profileImageUrl: string | null; + } | null; } export interface TrendingGame extends ShopAssets { diff --git a/yarn.lock b/yarn.lock index 2c321857..71551858 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2073,6 +2073,11 @@ redux-thunk "^3.1.0" reselect "^5.1.0" +"@remirror/core-constants@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" + integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== + "@remix-run/router@1.19.2": version "1.19.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" @@ -2840,6 +2845,201 @@ dependencies: uint8-util "^2.2.5" +"@tiptap/core@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.6.2.tgz#abda4116e4a39779fca7070e316b9ed9fdcded7e" + integrity sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg== + +"@tiptap/extension-blockquote@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.6.2.tgz#01b589565c87a691e586e189ddcbcdc5f35618fc" + integrity sha512-TSl41UZhi3ugJMDaf91CA4F5NeFylgTSm6GqnZAHOE6IREdCpAK3qej2zaW3EzfpzxW7sRGLlytkZRvpeyjgJA== + +"@tiptap/extension-bold@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.6.2.tgz#ed721961daf3210c7ba4433a5aeae981043c2d77" + integrity sha512-Q9KO8CCPCAXYqHzIw8b/ookVmrfqfCg2cyh9h9Hvw6nhO4LOOnJMcGVmWsrpFItbwCGMafI5iY9SbSj7RpCyuw== + +"@tiptap/extension-bubble-menu@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.6.2.tgz#237d84f217c8da52c0bc5265a36557fb27d64eaf" + integrity sha512-OF5CxCmYExcXZjcectwAeujSeDZ4IltPy+SsqBZLbQRDts9PQhzv5azGDvYdL2eMMkT3yhO2gWkXxSHMxI3O6w== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@tiptap/extension-bullet-list@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.6.2.tgz#be20b6c795c53bc0d199bdc4dd9f01b6270a1bee" + integrity sha512-Y5Uhir+za7xMm6RAe592aNNlLvCayVSQt2HfSckOr+c/v/Zd2bFUHv0ef6l/nUzUhDBs32Bg9SvfWx/yyMyNEw== + +"@tiptap/extension-code-block@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.6.2.tgz#cb3f6f607dcfb36e3eff25255fdcfdedfb3940a7" + integrity sha512-5jfoiQ/3AUrIyuVU1NmEXar6sZFnY7wDFf3ZU2zpcBUG++yg/CmpOe5bXpoolczhl58cM/jyBG5gumQjyOxLNg== + +"@tiptap/extension-code@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.6.2.tgz#5c6500d748fd4f52ddbe01ff114d4933c7a09e8f" + integrity sha512-U6jilbcpCxtLZAgJrTapXzzVJTXnS78kJITFSOLyGCTyGSm6PXatQ4hnaxVGmNet66GySONGjhwAVZ8+l94Rwg== + +"@tiptap/extension-document@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.6.2.tgz#5c3f3a85d12868f5d4e6d6d258b8fa0b8000b778" + integrity sha512-4qg3KWL3aO1M7hfDpZR6/vSo7Cfqr3McyGUfqb/BXqYDW1DwT8jJkDTcHrGU7WUKRlWgoyPyzM8pZiGlP0uQHg== + +"@tiptap/extension-dropcursor@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.6.2.tgz#22a64a4da25ac17cf0cd33e1e924762000152817" + integrity sha512-6R5sma/i2TKd5h9OpIcy3a0wOGp5BNT/zIgnE/1HTmKi40eNcCAVe8sxd6+iWA5ETONP1E48kDy4hqA5ZzZCiQ== + +"@tiptap/extension-floating-menu@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.6.2.tgz#cc9c97cdd5fa55407631d3135e00ca8051516444" + integrity sha512-ym7YMKGY3QhFUKUS6JYOwtdi8s2PeGmOhu7TwI9/U0LmGbELeKJBJl2BP1yB+Sjpv25pVL++CwJQ6dsrjDlZ8g== + +"@tiptap/extension-gapcursor@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.6.2.tgz#790c94d20a5b8ded4c0d38960254d24704a2bc08" + integrity sha512-gXg+EvUKlv3ZO1GxKkRmAsi/V4yyA8AzLW6ppOcYrM2CKf6epmPaVRgAjdwHCA6cm3QuCBJyWeGTCAjhjNakhw== + +"@tiptap/extension-hard-break@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.6.2.tgz#3c379d9104cd7d9e942277f22ba62c57fae267ad" + integrity sha512-ncuPBHhGY58QjluJvEH6vXotaa1QZ/vphXBGAr55kiATZwMIEHgwh2Hgc6AiFTcw057gabGn6jNFDfRB+HjbmA== + +"@tiptap/extension-heading@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.6.2.tgz#3884c309de60c9d61f1bb60c521410b3a0d88ed7" + integrity sha512-JQ2yjwXGAiwGc+MhS1mULBr354MHfmWqVDQLRg8ey6LkdXggTDDJ1Ni3GrUS7B5YcA/ICdhr4krXaQpNkT5Syw== + +"@tiptap/extension-horizontal-rule@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.6.2.tgz#f5680b3209bc48bf8635f3674355bd3d47f15622" + integrity sha512-3TlPqedPDM9QkRTUPhOTxNxQVPSsBwlsuLrAZOgyM1y871Xi7M1DFX0h9LLXuqzPndYzUY16NjrfBGFJX+O56w== + +"@tiptap/extension-italic@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.6.2.tgz#ea314f5e723499c9e7a1021ad7836693db9c653c" + integrity sha512-46zYKqM3o9w1A2G9hWr0ERGbJpqIncoH45XIfLdAI6ZldZVVf+NeXMGwjOPf4+03cZ5/emk3MRTnVp9vF4ToIg== + +"@tiptap/extension-link@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.6.2.tgz#5577d100cd3b735247db327b15d91de025cc76b6" + integrity sha512-3yiRDWa187h30e6iUOJeejZLsbzbJthLfBwTeJGx7pHh7RngsEW82npBRuqLoI3udhJGTkXbzwAFZ9qOGOjl1Q== + dependencies: + linkifyjs "^4.3.2" + +"@tiptap/extension-list-item@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.6.2.tgz#705f782a872e4bbb6f0e125fe277c45aeefe8161" + integrity sha512-ma/D2GKylpNB04FfNI3tDMY+C9nz7Yk85H21YTIGv8QL5KlDK97L6orydmx6IVRc2nNMZQVitBIEKDOXcczX9w== + +"@tiptap/extension-list-keymap@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.6.2.tgz#f14e173325b443a89dbbca7f418b76ec3d5c9a21" + integrity sha512-1kl/lggH+LL/FUwcSx8p761ebk9L5ZGK06mGyDDU9XiGLS310CktZYLnpEuFgn/oMPbRHo26oNl9SXLn1/U53A== + +"@tiptap/extension-list@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.6.2.tgz#beb4d965f48085fa7f69197e10109cde8c175046" + integrity sha512-ZLaEHGVq4eL26hZZFE9e7RArk2rEjcVstN/YTRTKElTnLaf58kLTKN3nlgy1PWGwzfWGUuXURBuEBLaq5l6djg== + +"@tiptap/extension-ordered-list@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.6.2.tgz#43b83757f67264ff0050c03825e780da43680c1d" + integrity sha512-KdJ5MLIw19N+XiqQ2COXGtaq9TzUbtlLE5dgYCJQ2EumeZKIGELvUnHjrnIB9gH/gRlMs+hprLTh23xVUDJovg== + +"@tiptap/extension-paragraph@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.6.2.tgz#d6cc89cdc369e463dd7dd4eb9121718441c984a0" + integrity sha512-jeJWj2xKib3392iHQEcB7wYZ30dUgXuwqpCTwtN9eANor+Zvv6CpDKBs1R2al6BYFbIJCgKeTulqxce0yoC80g== + +"@tiptap/extension-strike@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.6.2.tgz#2dab3f253a4ecfd525c5609ab5edb9325a6364c2" + integrity sha512-976u5WaioIN/0xCjl/UIEypmzACzxgVz6OGgfIsYyreMUiPjhhgzXb0A/2Po5p3nZpKcaMcxifOdhqdw+lDpIQ== + +"@tiptap/extension-text@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.6.2.tgz#77313173a9f91208e40d298bc2d40b39371b8fca" + integrity sha512-fFSUEv1H3lM92yr6jZdELk0gog8rPTK5hTf08kP8RsY8pA80Br1ADVenejrMV4UNTmT1JWTXGBGhMqfQFHUvAQ== + +"@tiptap/extension-underline@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.6.2.tgz#9f0dfb9722bd3d0cd144fc955bcb94a3fcf5eac2" + integrity sha512-IrG6vjxTMI2EeyhZCtx0sNTEu83PsAvzIh4vxmG1fUi/RYokks+sFbgGMuq0jtO96iVNEszlpAC/vaqfxFJwew== + +"@tiptap/extensions@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.6.2.tgz#591fbd5b9fa41f98f69dbd7d21d5d38a2241d94b" + integrity sha512-tg7/DgaI6SpkeawryapUtNoBxsJUMJl3+nSjTfTvsaNXed+BHzLPsvmPbzlF9ScrAbVEx8nj6CCkneECYIQ4CQ== + +"@tiptap/pm@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.6.2.tgz#2121d4917f92d11229529a26955a7033aa8a8843" + integrity sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ== + dependencies: + prosemirror-changeset "^2.3.0" + prosemirror-collab "^1.3.1" + prosemirror-commands "^1.6.2" + prosemirror-dropcursor "^1.8.1" + prosemirror-gapcursor "^1.3.2" + prosemirror-history "^1.4.1" + prosemirror-inputrules "^1.4.0" + prosemirror-keymap "^1.2.2" + prosemirror-markdown "^1.13.1" + prosemirror-menu "^1.2.4" + prosemirror-model "^1.24.1" + prosemirror-schema-basic "^1.2.3" + prosemirror-schema-list "^1.5.0" + prosemirror-state "^1.4.3" + prosemirror-tables "^1.6.4" + prosemirror-trailing-node "^3.0.0" + prosemirror-transform "^1.10.2" + prosemirror-view "^1.38.1" + +"@tiptap/react@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-3.6.2.tgz#5495776c9051a60ece7522da176c9f211a67c7df" + integrity sha512-jgG+bM/GDvI6jnqW3YyLtr/vOR6iO2ta9PYVzoWqNYIxISsMOJeRfinsIqB8l6hkiGZApn9bQji6oUXTc59fgA== + dependencies: + "@types/use-sync-external-store" "^0.0.6" + fast-deep-equal "^3.1.3" + use-sync-external-store "^1.4.0" + optionalDependencies: + "@tiptap/extension-bubble-menu" "^3.6.2" + "@tiptap/extension-floating-menu" "^3.6.2" + +"@tiptap/starter-kit@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.6.2.tgz#ddd5612d4836a87082254779c9f152bb51e757bc" + integrity sha512-nPzraIx/f1cOUNqG1LSC0OTnEu3mudcN3jQVuyGh3dvdOnik7FUciJEVfHKnloAyeoijidEeiLpiGHInp2uREg== + dependencies: + "@tiptap/core" "^3.6.2" + "@tiptap/extension-blockquote" "^3.6.2" + "@tiptap/extension-bold" "^3.6.2" + "@tiptap/extension-bullet-list" "^3.6.2" + "@tiptap/extension-code" "^3.6.2" + "@tiptap/extension-code-block" "^3.6.2" + "@tiptap/extension-document" "^3.6.2" + "@tiptap/extension-dropcursor" "^3.6.2" + "@tiptap/extension-gapcursor" "^3.6.2" + "@tiptap/extension-hard-break" "^3.6.2" + "@tiptap/extension-heading" "^3.6.2" + "@tiptap/extension-horizontal-rule" "^3.6.2" + "@tiptap/extension-italic" "^3.6.2" + "@tiptap/extension-link" "^3.6.2" + "@tiptap/extension-list" "^3.6.2" + "@tiptap/extension-list-item" "^3.6.2" + "@tiptap/extension-list-keymap" "^3.6.2" + "@tiptap/extension-ordered-list" "^3.6.2" + "@tiptap/extension-paragraph" "^3.6.2" + "@tiptap/extension-strike" "^3.6.2" + "@tiptap/extension-text" "^3.6.2" + "@tiptap/extension-underline" "^3.6.2" + "@tiptap/extensions" "^3.6.2" + "@tiptap/pm" "^3.6.2" + "@tokenizer/inflate@^0.2.6": version "0.2.7" resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.2.7.tgz#32dd9dfc9abe457c89b3d9b760fc0690c85a103b" @@ -3014,6 +3214,11 @@ dependencies: "@types/node" "*" +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + "@types/lodash-es@^4.17.12": version "4.17.12" resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz" @@ -3033,6 +3238,19 @@ dependencies: "@types/node" "*" +"@types/markdown-it@^14.0.0": + version "14.1.2" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" + integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" @@ -3125,6 +3343,11 @@ resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== + "@types/user-agents@^1.0.4": version "1.0.4" resolved "https://registry.npmjs.org/@types/user-agents/-/user-agents-1.0.4.tgz" @@ -4209,6 +4432,11 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +crelt@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" + integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== + cross-fetch-ponyfill@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/cross-fetch-ponyfill/-/cross-fetch-ponyfill-1.0.3.tgz#5c5524e3bd3374e71d5016c2327e416369a57527" @@ -6614,6 +6842,18 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + +linkifyjs@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.3.2.tgz#d97eb45419aabf97ceb4b05a7adeb7b8c8ade2b1" + integrity sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA== + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -6779,6 +7019,11 @@ lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== +lucide-react@^0.544.0: + version "0.544.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.544.0.tgz#4719953c10fd53a64dd8343bb0ed16ec79f3eeef" + integrity sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw== + magic-string@^0.30.17: version "0.30.17" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" @@ -6822,6 +7067,18 @@ make-fetch-happen@^10.2.1: socks-proxy-agent "^7.0.0" ssri "^9.0.0" +markdown-it@^14.0.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -6844,6 +7101,11 @@ maybe-combine-errors@^1.0.0: resolved "https://registry.yarnpkg.com/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz#e9592832e61fc47643a92cff3c1f33e27211e5be" integrity sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A== +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + meow@^12.0.1: version "12.1.1" resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6" @@ -7264,6 +7526,11 @@ ora@^5.1.0: strip-ansi "^6.0.0" wcwidth "^1.0.1" +orderedmap@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2" + integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g== + own-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" @@ -7499,6 +7766,160 @@ property-expr@^2.0.5: resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== +prosemirror-changeset@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz#eee3299cfabc7a027694e9abdc4e85505e9dd5e7" + integrity sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ== + dependencies: + prosemirror-transform "^1.0.0" + +prosemirror-collab@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33" + integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ== + dependencies: + prosemirror-state "^1.0.0" + +prosemirror-commands@^1.0.0, prosemirror-commands@^1.6.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz#d101fef85618b1be53d5b99ea17bee5600781b38" + integrity sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.10.2" + +prosemirror-dropcursor@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz#2ed30c4796109ddeb1cf7282372b3850528b7228" + integrity sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + prosemirror-view "^1.1.0" + +prosemirror-gapcursor@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz#5fa336b83789c6199a7341c9493587e249215cb4" + integrity sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ== + dependencies: + prosemirror-keymap "^1.0.0" + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-view "^1.0.0" + +prosemirror-history@^1.0.0, prosemirror-history@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.4.1.tgz#cc370a46fb629e83a33946a0e12612e934ab8b98" + integrity sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ== + dependencies: + prosemirror-state "^1.2.2" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.31.0" + rope-sequence "^1.3.0" + +prosemirror-inputrules@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.5.0.tgz#e22bfaf1d6ea4fe240ad447c184af3d520d43c37" + integrity sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz#c0f6ab95f75c0b82c97e44eb6aaf29cbfc150472" + integrity sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw== + dependencies: + prosemirror-state "^1.0.0" + w3c-keyname "^2.2.0" + +prosemirror-markdown@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz#863eb3fd5f57a444e4378174622b562735b1c503" + integrity sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g== + dependencies: + "@types/markdown-it" "^14.0.0" + markdown-it "^14.0.0" + prosemirror-model "^1.25.0" + +prosemirror-menu@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz#dea00e7b623cea89f4d76963bee22d2ac2343250" + integrity sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ== + dependencies: + crelt "^1.0.0" + prosemirror-commands "^1.0.0" + prosemirror-history "^1.0.0" + prosemirror-state "^1.0.0" + +prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0: + version "1.25.3" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.3.tgz#c657c60a361cb1e9c9f683d19118c0af50a6f7a9" + integrity sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA== + dependencies: + orderedmap "^2.0.0" + +prosemirror-schema-basic@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz#389ce1ec09b8a30ea9bbb92c58569cb690c2d695" + integrity sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ== + dependencies: + prosemirror-model "^1.25.0" + +prosemirror-schema-list@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz#5869c8f749e8745c394548bb11820b0feb1e32f5" + integrity sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.7.3" + +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.3.tgz#94aecf3ffd54ec37e87aa7179d13508da181a080" + integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.27.0" + +prosemirror-tables@^1.6.4: + version "1.8.1" + resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz#896a234e3e18240b629b747a871369dae78c8a9a" + integrity sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug== + dependencies: + prosemirror-keymap "^1.2.2" + prosemirror-model "^1.25.0" + prosemirror-state "^1.4.3" + prosemirror-transform "^1.10.3" + prosemirror-view "^1.39.1" + +prosemirror-trailing-node@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz#5bc223d4fc1e8d9145e4079ec77a932b54e19e04" + integrity sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ== + dependencies: + "@remirror/core-constants" "3.0.0" + escape-string-regexp "^4.0.0" + +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.3, prosemirror-transform@^1.7.3: + version "1.10.4" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.4.tgz#56419eac14f9f56612c806ae46f9238648f3f02e" + integrity sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw== + dependencies: + prosemirror-model "^1.21.0" + +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.39.1: + version "1.41.2" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.2.tgz#e69ad3883bfd3c9f3c9cf6da5cee940210df0b6f" + integrity sha512-PGS/jETmh+Qjmre/6vcG7SNHAKiGc4vKOJmHMPRmvcUl7ISuVtrtHmH06UDUwaim4NDJfZfVMl7U7JkMMETa6g== + dependencies: + prosemirror-model "^1.20.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" @@ -7517,6 +7938,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -7901,6 +8327,11 @@ rollup@^4.20.0: "@rollup/rollup-win32-x64-msvc" "4.23.0" fsevents "~2.3.2" +rope-sequence@^1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425" + integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ== + rrweb-cssom@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" @@ -8902,6 +9333,11 @@ typescript@^5.3.3, typescript@^5.4.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + uint8-util@^2.2.2, uint8-util@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/uint8-util/-/uint8-util-2.2.5.tgz#f1a8ff800e4e10a3ac1c82ee3667c99245123896" @@ -9021,6 +9457,11 @@ use-sync-external-store@^1.0.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== +use-sync-external-store@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== + user-agents@^1.1.387: version "1.1.387" resolved "https://registry.yarnpkg.com/user-agents/-/user-agents-1.1.387.tgz#afc69da00b50eee7ffa17724890e755a6672b99f" @@ -9087,6 +9528,11 @@ void-elements@3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== +w3c-keyname@^2.2.0: + version "2.2.8" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" + integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== + w3c-xmlserializer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c"