From 5be7228b38d71c6b6c6c64f6d0a384818d4db85d Mon Sep 17 00:00:00 2001 From: sevichecc <91365763+Sevichecc@users.noreply.github.com> Date: Sun, 16 Apr 2023 16:58:49 +0800 Subject: [PATCH] feat: add view bookmark command --- package.json | 24 ++++++++++++--- pnpm-lock.yaml | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ src/api.ts | 18 ++++++++--- src/bookmark.tsx | 65 +++++++++++++++++++++++++++++++++++++++ src/types.ts | 26 ++++++++++++++++ 5 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 src/bookmark.tsx diff --git a/package.json b/package.json index 1fc3894..60390ff 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,22 @@ "title": "Add Status", "description": "Publish status with attenchments, or scheduled status", "mode": "view" + }, + { + "name": "bookmark", + "title": "View Bookmarks", + "description": "View your bookmarked statuses", + "mode": "view", + "preferences": [ + { + "name": "bookmarkLimit", + "type": "textfield", + "required": false, + "title": "Maximum number of bookmarks", + "description": "Maximum number of bookmarks", + "placeholder": "default value : 20" + } + ] } ], "preferences": [ @@ -30,7 +46,6 @@ "required": true, "title": "Akkoma instance's URL", "description": "Your Akkoma / Pleroma instance's URL", - "link": "https://github.com/Sevichecc/raycast-akkoma-extension", "placeholder": "such as: example.dev" }, { @@ -65,12 +80,13 @@ ], "dependencies": { "@raycast/api": "^1.49.3", - "node-fetch": "^3.3.1" + "node-fetch": "^3.3.1", + "node-html-markdown": "^1.3.0" }, "devDependencies": { - "@types/node-fetch": "^3.0.3", "@raycast/eslint-config": "1.0.5", "@types/node": "18.8.3", + "@types/node-fetch": "^3.0.3", "@types/react": "18.0.9", "eslint": "^7.32.0", "prettier": "^2.5.1", @@ -83,4 +99,4 @@ "lint": "ray lint", "publish": "npx @raycast/api@latest publish" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b571ba9..614e4ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,9 @@ dependencies: node-fetch: specifier: ^3.3.1 version: 3.3.1 + node-html-markdown: + specifier: ^1.3.0 + version: 1.3.0 devDependencies: '@raycast/eslint-config': @@ -413,6 +416,10 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true + /boolbase@1.0.0: + resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=} + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -483,6 +490,21 @@ packages: which: 2.0.2 dev: true + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.0.1 + nth-check: 2.1.1 + dev: false + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: false + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -520,6 +542,33 @@ packages: esutils: 2.0.3 dev: true + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: false + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils@3.0.1: + resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: false + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true @@ -531,6 +580,11 @@ packages: ansi-colors: 4.1.3 dev: true + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: false + /escape-string-regexp@1.0.5: resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: '>=0.8.0'} @@ -800,6 +854,11 @@ packages: engines: {node: '>=8'} dev: true + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: false + /ignore@4.0.6: resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} engines: {node: '>= 4'} @@ -956,6 +1015,26 @@ packages: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + /node-html-markdown@1.3.0: + resolution: {integrity: sha512-OeFi3QwC/cPjvVKZ114tzzu+YoR+v9UXW5RwSXGUqGb0qCl0DvP406tzdL7SFn8pZrMyzXoisfG2zcuF9+zw4g==} + engines: {node: '>=10.0.0'} + dependencies: + node-html-parser: 6.1.5 + dev: false + + /node-html-parser@6.1.5: + resolution: {integrity: sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg==} + dependencies: + css-select: 5.1.0 + he: 1.2.0 + dev: false + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: false + /once@1.4.0: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: diff --git a/src/api.ts b/src/api.ts index 8fcb112..1f1d5a5 100644 --- a/src/api.ts +++ b/src/api.ts @@ -10,6 +10,7 @@ import { Account, StatusAttachment, UploadAttachResponse, + BookmarkedStatus, } from "./types"; import { client } from "./oauth"; import { RequestInit, Response } from "node-fetch"; @@ -22,6 +23,7 @@ const CONFIG = { statusesUrl: "/api/v1/statuses", verifyCredentialsUrl: "/api/v1/accounts/verify_credentials", mediaUrl: "/api/v1/media/", + bookmarkUrl: "/api/v1/bookmarks", }; const apiUrl = (instance: string, path: string): string => `https://${instance}${path}`; @@ -75,9 +77,7 @@ const postNewStatus = async (statusOptions: Partial): Promise => { - const response = await fetchWithAuth(apiUrl(instance, CONFIG.verifyCredentialsUrl), { - method: "GET", - }); + const response = await fetchWithAuth(apiUrl(instance, CONFIG.verifyCredentialsUrl)); if (!response.ok) throw new Error("Failed to fetch account's info :("); return (await response.json()) as Account; @@ -101,4 +101,14 @@ const uploadAttachment = async ({ file, description }: StatusAttachment): Promis return (await response.json()) as UploadAttachResponse; }; -export default { fetchToken, createApp, postNewStatus, fetchAccountInfo, uploadAttachment }; +const fetchBookmarks = async (): Promise => { + const { bookmarkLimit } = getPreferenceValues(); + const url = bookmarkLimit ? CONFIG.bookmarkUrl + `?&limit=${bookmarkLimit}` : CONFIG.bookmarkUrl; + + const response = await fetchWithAuth(apiUrl(instance, url)); + if (!response.ok) throw new Error("Could not fetch bookmarks"); + + return (await response.json()) as BookmarkedStatus[]; +}; + +export default { fetchToken, createApp, postNewStatus, fetchAccountInfo, uploadAttachment, fetchBookmarks }; diff --git a/src/bookmark.tsx b/src/bookmark.tsx new file mode 100644 index 0000000..a19b720 --- /dev/null +++ b/src/bookmark.tsx @@ -0,0 +1,65 @@ +import { useEffect, useState } from "react"; +import { Action, ActionPanel, List, Toast, showToast, Cache } from "@raycast/api"; +import { BookmarkedStatus, AkkomaError } from "./types"; +import { NodeHtmlMarkdown } from "node-html-markdown"; +import apiServer from "./api"; + +const cache = new Cache(); + +export default function BookmarkCommand() { + const cached = cache.get("latest_bookmarks"); + const [bookmarks, setBookmarks] = useState(cached ? JSON.parse(cached) : []); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const getBookmark = async () => { + try { + showToast(Toast.Style.Animated, "Loaing bookmarks..."); + const newBookmarks = await apiServer.fetchBookmarks(); + setBookmarks(newBookmarks); + cache.set("latest_bookmarks", JSON.stringify(newBookmarks)); + showToast(Toast.Style.Success, "Bookmarked has been loaded"); + } catch (error) { + const requestErr = error as AkkomaError; + showToast(Toast.Style.Failure, "Error", requestErr.message); + } finally { + setIsLoading(false); + } + }; + getBookmark(); + }, []); + + const parseStatus = ({ content, media_attachments, account, created_at }: BookmarkedStatus) => { + const nhm = new NodeHtmlMarkdown(); + + const images = media_attachments.filter((attachment) => attachment.type === "image"); + const parsedImages = images.reduce((link, image) => link + `![${image.description}](${image.remote_url})`, ""); + + const date = new Date(created_at); + const parseTime = new Intl.DateTimeFormat("default", { + hour: "numeric", + minute: "numeric", + day: "numeric", + month: "long", + }).format(date); + + return ` _@${account.acct} (${parseTime})_ ` + nhm.translate("
" + content) + parsedImages; + }; + + return ( + + {bookmarks?.map((bookmark) => ( + } + actions={ + + + + } + /> + ))} + + ); +} diff --git a/src/types.ts b/src/types.ts index bdd2240..17f9748 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,6 +5,7 @@ export type VisibilityScope = "public" | "unlisted" | "direct" | "private" | "lo export interface Preference { instance: string; defaultVisibility: VisibilityScope; + bookmarkLimit: string; } export interface VisibilityOption { @@ -60,6 +61,23 @@ export interface Status { to?: string[]; } +export interface BookmarkedStatus { + created_at: Date; + media_attachments: UploadAttachResponse[]; + account: { + acct: string; + }; + url: string; + content: string; + pleroma: { + content: { + "text/plain": string; + }; + }; + id: string; + fqn: string; +} + // API Responses export interface ApiResponse { id: number; @@ -90,8 +108,16 @@ export interface StatusAttachment { } export interface UploadAttachResponse { + blurhash: string; description: string | null; id: string; + meta: { + original: { + aspect: number; + height: number; + width: number; + }; + }; pleroma: { mime_type: string; };