diff --git a/package.json b/package.json index 3a85cea..b94cb30 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "html2canvas": "^1.0.0-rc.7", "immer": "^8.0.1", "joi": "^17.4.0", + "js-cookie": "^3.0.1", "localforage": "^1.9.0", "lodash": "^4.17.21", "mustache": "^4.2.0", diff --git a/src/components/Announcement/Announcement.tsx b/src/components/Announcement/Announcement.tsx index 9dda960..0e28744 100644 --- a/src/components/Announcement/Announcement.tsx +++ b/src/components/Announcement/Announcement.tsx @@ -5,6 +5,7 @@ import { useLocation } from 'react-use'; import logo from '../../../assets/logos/libel.png'; import { displayName } from '../../../package.json'; import * as TEXTS from '../../constants/texts'; +import { dontShowAgain, promptDontShowAgain } from '../../helpers/announecement'; import { isViewport, Viewport } from '../../helpers/responsive'; import lihkgSelectors from '../../stylesheets/variables/lihkg/selectors.scss'; import { IconName } from '../../types/icon'; @@ -14,6 +15,7 @@ import styles from './Announcement.scss'; interface IProps extends React.HTMLAttributes { icon?: IconName; + forced?: boolean; } const announcementElements: HTMLDivElement[] = []; @@ -38,12 +40,15 @@ const updateLayout = () => { }; const Announcement: React.FunctionComponent = (props) => { - const { className, icon, children } = props; + const { id, className, icon, forced = false, children } = props; const [showed, setShowed] = useState(true); const handleClose: React.MouseEventHandler = (event) => { event.preventDefault(); setShowed(false); + if (id && !forced && promptDontShowAgain()) { + dontShowAgain(id, 7); + } }; const announcementRef = useRef(null); diff --git a/src/components/NewVersionAnnouncement/NewVersionAnnouncement.tsx b/src/components/NewVersionAnnouncement/NewVersionAnnouncement.tsx index 383227a..abf84ba 100644 --- a/src/components/NewVersionAnnouncement/NewVersionAnnouncement.tsx +++ b/src/components/NewVersionAnnouncement/NewVersionAnnouncement.tsx @@ -22,7 +22,7 @@ const NewVersionAnnouncement: React.FunctionComponent = (props) => { const oldVersionMessage = render(versionUpdate.oldVersionMessage, { currentVersion }); const newVersionMessage = render(versionUpdate.newVersionMessage, { newVersion }); return ( - + {newVersionMessage} diff --git a/src/constants/storage.ts b/src/constants/storage.ts index b8113f7..900d909 100644 --- a/src/constants/storage.ts +++ b/src/constants/storage.ts @@ -4,5 +4,3 @@ export const DRAFTS_KEY = 'drafts'; export const DEPRECATED_LOCAL_STORAGE_DATA_KEYS = [ 'LIHKG-Label-Users-data' ]; - -export const ANNOUNCEMENTS_KEY = 'LIHKG-Announcements'; diff --git a/src/constants/texts.ts b/src/constants/texts.ts index fbb749c..27d60bc 100644 --- a/src/constants/texts.ts +++ b/src/constants/texts.ts @@ -52,6 +52,7 @@ export const SUBSCRIPTION_VALIDATION_ERROR = '標籤名單格式錯誤,無法 // announcement export const CHANGE_LOG = '更新內容'; export const ANNOUNCEMENT_CLOSE_BUTTON_TEXT = '關閉公告'; +export const ANNOUNCEMENT_DONT_SHOW_AGAIN_QUESTION = '七天內不再顯示此公告?'; // info export const SOURCE_CODE = 'Source code'; diff --git a/src/helpers/announecement.ts b/src/helpers/announecement.ts new file mode 100644 index 0000000..56c1342 --- /dev/null +++ b/src/helpers/announecement.ts @@ -0,0 +1,25 @@ +import { namespace } from '../../package.json'; +import { ANNOUNCEMENT_DONT_SHOW_AGAIN_QUESTION } from './../constants/texts'; +import * as cookies from './cookies'; + +const prefix = `${namespace}-announcement-read-receipt`; + +const getCookieName = (id: string) => { + const name = `${prefix}-${id}`; + return name; +}; + +export const hasRead = (id: string) => { + const name = getCookieName(id); + const value = cookies.get(name); + return value || false; +}; + +export const dontShowAgain = (id: string, expires: number) => { + const name = getCookieName(id); + cookies.set(name, true, { expires }); +}; + +export const promptDontShowAgain = () => { + return window.confirm(ANNOUNCEMENT_DONT_SHOW_AGAIN_QUESTION); +}; diff --git a/src/helpers/cookies.ts b/src/helpers/cookies.ts new file mode 100644 index 0000000..d214ab5 --- /dev/null +++ b/src/helpers/cookies.ts @@ -0,0 +1,15 @@ +import Cookies, { CookieAttributes } from 'js-cookie'; + +export const get = (name: string): T | undefined => { + const json = Cookies.get(name); + return json && JSON.parse(json); +}; + +export const set = (name: string, value: T, options?: CookieAttributes) => { + const _options = { + expires: 7, + ...options + }; + const json = JSON.stringify(value); + return Cookies.set(name, json, _options); +}; diff --git a/src/models/App.tsx b/src/models/App.tsx index b5dd4e5..ab6fe41 100644 --- a/src/models/App.tsx +++ b/src/models/App.tsx @@ -5,6 +5,7 @@ import Announcement from '../components/Announcement/Announcement'; import NewVersionAnnouncement from '../components/NewVersionAnnouncement/NewVersionAnnouncement'; import * as ATTRIBUTES from '../constants/attributes'; import * as REGEXES from '../constants/regexes'; +import { hasRead } from '../helpers/announecement'; import * as LIHKG from '../helpers/lihkg'; import { checkUpdate } from '../helpers/version'; import { intercept } from '../helpers/xhr'; @@ -126,10 +127,10 @@ class App { const announcements = await fetchAnnouncements(); const now = Date.now(); for (const announcement of announcements) { - const { icon, body, endAt } = announcement; - if (!endAt || now <= endAt) { + const { id, icon, body, endAt } = announcement; + if ((!id || !hasRead(id)) && (!endAt || now <= endAt)) { LIHKG.renderAnnouncement( - + ); diff --git a/src/types/announcement.ts b/src/types/announcement.ts index 2332d94..90a3c46 100644 --- a/src/types/announcement.ts +++ b/src/types/announcement.ts @@ -1,6 +1,7 @@ import { IconName } from './icon'; export interface IAnnouncement { + id?: string; // added in 1.0.18 icon?: IconName; body: string; endAt?: number; diff --git a/yarn.lock b/yarn.lock index fe9e7df..8d87f7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3420,6 +3420,11 @@ js-cookie@^2.2.1: resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== +js-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414" + integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"