import { parse } from "date-fns"; import filter from "lodash/filter"; import pick from "lodash/pick"; import property from "lodash/property"; import values from "lodash/values"; import WaitQueue from "wait-queue"; import { markdownConverter } from "markdown"; export const seenPostsLSKey = "cf2021_seen_posts"; export const seenAnnouncementsLSKey = "cf2021_seen_announcements"; /** * Filter & sort collection of posts. * @param {CF2021.PostStoreFilters} filters * @param {CF2021.PostStoreItems} allItems * @returns {CF2021.Post[]} */ export const filterPosts = (filters, allItems) => { const predicate = {}; if (filters.flags === "active") { predicate.archived = false; } if (filters.flags === "archived") { predicate.archived = true; } if (filters.type === "proposalsOnly") { predicate.type = "procedure-proposal"; } if (filters.type === "discussionOnly") { predicate.type = "post"; } let filteredItems = filter(allItems, predicate); if (!filters.showPendingProposals) { filteredItems = filteredItems.filter( (item) => item.type === "post" || item.state !== "pending" ); } if (filters.sort === "byDate") { return filteredItems.sort((a, b) => b.datetime - a.datetime); } return filteredItems.sort((a, b) => b.ranking.score - a.ranking.score); }; /** * Update current posts window according to used filters. * @param {CF2021.PostStorePayload} state */ export const updateWindowPosts = (state) => { state.window.items = filterPosts(state.filters, values(state.items)).map( property("id") ); }; /** * Update itemIds from items. * @param {CF2021.AnnouncementStorePayload} state */ export const syncAnnoucementItemIds = (state) => { state.itemIds = values(state.items) .sort((a, b) => b.datetime - a.datetime) .map((announcement) => announcement.id); }; export const postsMyVoteMapping = { 0: "none", 1: "like", [-1]: "dislike", }; export const postsTypeMapping = { 0: "procedure-proposal", 1: "post", }; export const postsTypeMappingRev = { post: 1, "procedure-proposal": 0, }; export const postsStateMapping = { 0: "pending", 1: "announced", 2: "accepted", 3: "rejected", 4: "rejected-by-chairman", }; export const postsStateMappingRev = { pending: 0, announced: 1, accepted: 2, rejected: 3, "rejected-by-chairman": 4, }; export const announcementTypeMapping = { 0: "rejected-procedure-proposal", 1: "accepted-procedure-proposal", 2: "suggested-procedure-proposal", 3: "voting", 4: "announcement", 5: "user-ban", }; export const announcementTypeMappingRev = { "rejected-procedure-proposal": 0, "accepted-procedure-proposal": 1, "suggested-procedure-proposal": 2, voting: 3, announcement: 4, "user-ban": 5, }; /** * Parse single post from the API. * * @param {any} rawPost * @returns {CF2021.Post} */ export const parseRawPost = (rawPost) => { const post = { ...pick(rawPost, ["id", "content", "author"]), contentHtml: markdownConverter.makeHtml(rawPost.content), datetime: parse(rawPost.datetime, "yyyy-MM-dd HH:mm:ss", new Date()), historyLog: rawPost.history_log, ranking: { dislikes: rawPost.ranking.dislikes, likes: rawPost.ranking.likes, score: rawPost.ranking.score, myVote: postsMyVoteMapping[rawPost.ranking.my_vote], }, type: postsTypeMapping[rawPost.type], modified: Boolean(rawPost.is_changed), archived: Boolean(rawPost.is_archived), hidden: false, seen: isSeen(seenPostsLSKey, rawPost.id), }; if (post.type === "procedure-proposal") { post.state = postsStateMapping[rawPost.state]; } return post; }; /** * Parse single announcement from the API. * * @param {any} rawAnnouncement * @returns {CF2021.Announcement} */ export const parseRawAnnouncement = (rawAnnouncement) => { const announcement = { ...pick(rawAnnouncement, ["id", "content", "link"]), datetime: parse( rawAnnouncement.datetime, "yyyy-MM-dd HH:mm:ss", new Date() ), type: announcementTypeMapping[rawAnnouncement.type], seen: isSeen(seenAnnouncementsLSKey, rawAnnouncement.id), }; return announcement; }; export const createSeenWriter = (localStorageKey) => { const queue = new WaitQueue(); const seenWriterWorker = async () => { const id = await queue.shift(); const seen = new Set( (localStorage.getItem(localStorageKey) || "").split(",") ); seen.add(id); localStorage.setItem(localStorageKey, Array.from(seen).join(",")); setTimeout(seenWriterWorker); }; seenWriterWorker(); return { markSeen: (id) => queue.push(id), }; }; export const isSeen = (localStorageKey, id) => { const val = localStorage.getItem(localStorageKey) || ""; return val.indexOf(id) !== -1; };