-
Notifications
You must be signed in to change notification settings - Fork 0
Add in-site notification feature to Docusaurus #814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
f9d9b14
Add `@fortawesome/fontawesome-free`
josh-wong b2f2b84
Create NotificationBell.tsx
josh-wong 56178b2
Create index.tsx
josh-wong d1c097f
Create notifications.js
josh-wong cf09623
Add custom notification function
josh-wong 1fa1b43
Add styles for notification icon and dropdown
josh-wong 4ddf8b0
Fix question mark tooltip overlap issue
josh-wong 02dbca2
Merge branch 'main' into docusaurus/add-in-site-notification-feature
josh-wong cf699a0
Fix spelling error
josh-wong c7cb742
Adjust notification styles for dropdown consistency
josh-wong 9a06f12
Fix image background color on zoom
josh-wong 08270cd
Make notification message font larger
josh-wong bc5faaf
Fix broken relative links
josh-wong fd0b92d
Add missing ending curly bracket
josh-wong 261b083
Add notification messages
josh-wong 53232c3
Remove duplicate bell icon
josh-wong 439b581
Fix grammar
josh-wong 5f8e38c
Change object name from `languages` to `message`
josh-wong 1bbb91d
Add automatic version tracking for content changes
josh-wong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import React, { useState, useEffect, useRef } from 'react'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { faBell } from '@fortawesome/free-regular-svg-icons'; | ||
import { detectLanguage } from '../data/notifications'; | ||
|
||
const NotificationBell = ({ notifications }) => { | ||
const [isOpen, setIsOpen] = useState(false); | ||
const [notificationList, setNotificationList] = useState([]); | ||
const [currentLanguage, setCurrentLanguage] = useState('en'); | ||
const dropdownRef = useRef(null); | ||
const wrapperRef = useRef(null); | ||
|
||
// Set the current language in the component. | ||
useEffect(() => { | ||
setCurrentLanguage(detectLanguage()); | ||
}, []); | ||
|
||
// Toggle dropdown visibility and prevent the event from bubbling up to the outside click handler. | ||
const toggleDropdown = (event) => { | ||
event.stopPropagation(); // Prevent outside click handler from immediately reopening dropdown. | ||
setIsOpen((prev) => !prev); | ||
}; | ||
|
||
// Load notifications from localStorage and update it if there are new notifications. | ||
useEffect(() => { | ||
// Retrieve seen notifications from localStorage. | ||
const seenNotifications = JSON.parse(localStorage.getItem('seenNotifications')) || []; | ||
|
||
// Map the notifications to add read status based on seenNotifications. | ||
const updatedNotifications = notifications.map(notification => ({ | ||
...notification, | ||
read: seenNotifications.includes(notification.id), | ||
})); | ||
|
||
// Set the updated notifications in state. | ||
setNotificationList(updatedNotifications); | ||
}, [notifications]); // Dependency ensures rerun if notifications change. | ||
|
||
// Save changes to localStorage when notifications are clicked. | ||
const handleNotificationClick = (notification, event) => { | ||
// If it's an internal link, prevent default behavior and use history to navigate. | ||
if (!notification.isExternal) { | ||
event.preventDefault(); | ||
window.location.href = notification.url; | ||
} | ||
|
||
const updatedList = notificationList.map(notif => | ||
notif.id === notification.id ? { ...notif, read: true } : notif | ||
); | ||
|
||
// Save the seen notifications in localStorage. | ||
const seenNotifications = updatedList | ||
.filter(notif => notif.read) | ||
.map(notif => notif.id); | ||
|
||
localStorage.setItem('seenNotifications', JSON.stringify(seenNotifications)); | ||
setNotificationList(updatedList); // Update the notification list with a read status. | ||
}; | ||
|
||
// Count unread notifications. | ||
const unreadCount = notificationList.filter(notification => !notification.read).length; | ||
|
||
// Close the dropdown when clicking outside. | ||
useEffect(() => { | ||
const handleClickOutside = (event) => { | ||
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) { | ||
setIsOpen(false); | ||
} | ||
}; | ||
|
||
document.addEventListener('mousedown', handleClickOutside); | ||
return () => document.removeEventListener('mousedown', handleClickOutside); | ||
}, []); | ||
|
||
return ( | ||
<div className="notification-wrapper" onClick={toggleDropdown} ref={wrapperRef}> | ||
<i className="fa-solid fa-bell"></i><FontAwesomeIcon icon={faBell} size="lg" /> | ||
{unreadCount > 0 && <span className="notification-count">{unreadCount}</span>} | ||
{isOpen && ( | ||
<div className="notification-dropdown" ref={dropdownRef}> | ||
{notificationList.map(notification => ( | ||
<a | ||
key={notification.id} | ||
href={notification.url} | ||
className={`notification-item ${!notification.read ? 'unread' : ''}`} | ||
onClick={(e) => handleNotificationClick(notification, e)} | ||
target={notification.isExternal ? '_blank' : '_self'} | ||
rel={notification.isExternal ? 'noopener noreferrer' : undefined} | ||
> | ||
{notification.message} | ||
</a> | ||
))} | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default NotificationBell; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// This file contains the notifications data and a function to retrieve it. | ||
// The notifications are stored in an array of objects, each containing a message in multiple languages and URLs for those messages. | ||
const notificationsList = [ | ||
{ | ||
languages: { | ||
en: 'Discover how to use generic contracts and functions in ScalarDL', | ||
ja: 'ScalarDL で汎用コントラクトおよびファンクションの使用方法を学ぶ' | ||
}, | ||
url: { | ||
en: 'use-generic-contracts?utm_source=docs-site&utm_medium=notifications', | ||
ja: 'use-generic-contracts?utm_source=docs-site&utm_medium=notifications' | ||
}, | ||
unread: true | ||
}, | ||
{ | ||
languages: { | ||
en: 'Blog post: Migrating from Amazon QLDB to ScalarDL', | ||
ja: 'ブログ記事: データベースエンジニアリングの最新トレンドとベストプラクティスを学ぶ DBEM #6 のハイライト' | ||
}, | ||
url: { | ||
en: 'https://medium.com/scalar-engineering/migrating-from-amazon-qldb-to-scalardl-ad6ffacbf598?utm_source=docs-site&utm_medium=notifications', | ||
ja: 'https://medium.com/scalar-engineering-ja/database-engineering-meetup-%E7%AC%AC6%E5%9B%9E-dbem-6-%E3%82%92%E9%96%8B%E5%82%AC%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F-fccde39d2926?utm_source=docs-site&utm_medium=notifications' | ||
}, | ||
unread: true | ||
}, | ||
{ | ||
languages: { | ||
en: 'Learn how to organize your data based on the ScalarDL data model', | ||
ja: 'ScalarDL データモデルに基づいたデータの整理方法を学ぼう' | ||
}, | ||
url: { | ||
en: 'data-modeling?utm_source=docs-site&utm_medium=notifications', | ||
ja: 'data-modeling?utm_source=docs-site&utm_medium=notifications' | ||
}, | ||
unread: true | ||
} | ||
]; | ||
|
||
// Update the getNotifications function to handle both single URL and language-specific URLs, and prepend the correct base URL for relative paths. | ||
export const getNotifications = (language = 'en') => { | ||
const totalNotifications = notificationsList.length; | ||
|
||
// Define base URLs for different languages. | ||
const baseUrls = { | ||
en: '/docs/latest/', | ||
ja: '/ja-jp/docs/latest/' | ||
}; | ||
|
||
const currentDomain = 'scalardl.scalar-labs.com'; | ||
|
||
return notificationsList | ||
.map((notification, index) => { | ||
// Get the appropriate URL for the language | ||
let url = typeof notification.url === 'object' | ||
? notification.url[language] || notification.url.en | ||
: notification.url; | ||
|
||
// If the URL is relative (doesn't start with http), prepend the appropriate base URL. | ||
if (url && !url.startsWith('http')) { | ||
url = baseUrls[language] + url; | ||
} | ||
|
||
// Check if the link is external by checking the domain. | ||
const isExternal = url.startsWith('http') && !url.includes(currentDomain); | ||
|
||
return { | ||
id: totalNotifications - index, | ||
message: notification.languages[language] || notification.languages.en, | ||
url: url, | ||
isExternal: isExternal, // Add this flag for the component to use. | ||
unread: notification.unread | ||
}; | ||
}) | ||
.sort((a, b) => b.id - a.id); | ||
}; | ||
|
||
// Utility function that detects the language from the URL path. | ||
export const detectLanguage = () => { | ||
if (typeof window !== 'undefined') { | ||
return window.location.pathname.includes('ja-jp') ? 'ja' : 'en'; | ||
} | ||
return 'en'; // Default notifications to English if Japanese is not detected for some reason. | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.