mirror of
https://github.com/Sevichecc/raycast-akkoma-extension.git
synced 2025-04-30 06:39:30 +08:00
feat: add view bookmark command
This commit is contained in:
parent
c66283b283
commit
5be7228b38
5 changed files with 204 additions and 8 deletions
22
package.json
22
package.json
|
@ -21,6 +21,22 @@
|
||||||
"title": "Add Status",
|
"title": "Add Status",
|
||||||
"description": "Publish status with attenchments, or scheduled status",
|
"description": "Publish status with attenchments, or scheduled status",
|
||||||
"mode": "view"
|
"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": [
|
"preferences": [
|
||||||
|
@ -30,7 +46,6 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"title": "Akkoma instance's URL",
|
"title": "Akkoma instance's URL",
|
||||||
"description": "Your Akkoma / Pleroma instance's URL",
|
"description": "Your Akkoma / Pleroma instance's URL",
|
||||||
"link": "https://github.com/Sevichecc/raycast-akkoma-extension",
|
|
||||||
"placeholder": "such as: example.dev"
|
"placeholder": "such as: example.dev"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -65,12 +80,13 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@raycast/api": "^1.49.3",
|
"@raycast/api": "^1.49.3",
|
||||||
"node-fetch": "^3.3.1"
|
"node-fetch": "^3.3.1",
|
||||||
|
"node-html-markdown": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node-fetch": "^3.0.3",
|
|
||||||
"@raycast/eslint-config": "1.0.5",
|
"@raycast/eslint-config": "1.0.5",
|
||||||
"@types/node": "18.8.3",
|
"@types/node": "18.8.3",
|
||||||
|
"@types/node-fetch": "^3.0.3",
|
||||||
"@types/react": "18.0.9",
|
"@types/react": "18.0.9",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
|
|
|
@ -7,6 +7,9 @@ dependencies:
|
||||||
node-fetch:
|
node-fetch:
|
||||||
specifier: ^3.3.1
|
specifier: ^3.3.1
|
||||||
version: 3.3.1
|
version: 3.3.1
|
||||||
|
node-html-markdown:
|
||||||
|
specifier: ^1.3.0
|
||||||
|
version: 1.3.0
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@raycast/eslint-config':
|
'@raycast/eslint-config':
|
||||||
|
@ -413,6 +416,10 @@ packages:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/boolbase@1.0.0:
|
||||||
|
resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/brace-expansion@1.1.11:
|
/brace-expansion@1.1.11:
|
||||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -483,6 +490,21 @@ packages:
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: true
|
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:
|
/csstype@3.1.2:
|
||||||
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
|
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
|
||||||
|
|
||||||
|
@ -520,6 +542,33 @@ packages:
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
dev: true
|
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:
|
/emoji-regex@8.0.0:
|
||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -531,6 +580,11 @@ packages:
|
||||||
ansi-colors: 4.1.3
|
ansi-colors: 4.1.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/entities@4.5.0:
|
||||||
|
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/escape-string-regexp@1.0.5:
|
/escape-string-regexp@1.0.5:
|
||||||
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
|
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
|
@ -800,6 +854,11 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/he@1.2.0:
|
||||||
|
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ignore@4.0.6:
|
/ignore@4.0.6:
|
||||||
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
|
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
@ -956,6 +1015,26 @@ packages:
|
||||||
fetch-blob: 3.2.0
|
fetch-blob: 3.2.0
|
||||||
formdata-polyfill: 4.0.10
|
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:
|
/once@1.4.0:
|
||||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
18
src/api.ts
18
src/api.ts
|
@ -10,6 +10,7 @@ import {
|
||||||
Account,
|
Account,
|
||||||
StatusAttachment,
|
StatusAttachment,
|
||||||
UploadAttachResponse,
|
UploadAttachResponse,
|
||||||
|
BookmarkedStatus,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { client } from "./oauth";
|
import { client } from "./oauth";
|
||||||
import { RequestInit, Response } from "node-fetch";
|
import { RequestInit, Response } from "node-fetch";
|
||||||
|
@ -22,6 +23,7 @@ const CONFIG = {
|
||||||
statusesUrl: "/api/v1/statuses",
|
statusesUrl: "/api/v1/statuses",
|
||||||
verifyCredentialsUrl: "/api/v1/accounts/verify_credentials",
|
verifyCredentialsUrl: "/api/v1/accounts/verify_credentials",
|
||||||
mediaUrl: "/api/v1/media/",
|
mediaUrl: "/api/v1/media/",
|
||||||
|
bookmarkUrl: "/api/v1/bookmarks",
|
||||||
};
|
};
|
||||||
|
|
||||||
const apiUrl = (instance: string, path: string): string => `https://${instance}${path}`;
|
const apiUrl = (instance: string, path: string): string => `https://${instance}${path}`;
|
||||||
|
@ -75,9 +77,7 @@ const postNewStatus = async (statusOptions: Partial<Status>): Promise<StatusResp
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchAccountInfo = async (): Promise<Account> => {
|
const fetchAccountInfo = async (): Promise<Account> => {
|
||||||
const response = await fetchWithAuth(apiUrl(instance, CONFIG.verifyCredentialsUrl), {
|
const response = await fetchWithAuth(apiUrl(instance, CONFIG.verifyCredentialsUrl));
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to fetch account's info :(");
|
if (!response.ok) throw new Error("Failed to fetch account's info :(");
|
||||||
return (await response.json()) as Account;
|
return (await response.json()) as Account;
|
||||||
|
@ -101,4 +101,14 @@ const uploadAttachment = async ({ file, description }: StatusAttachment): Promis
|
||||||
return (await response.json()) as UploadAttachResponse;
|
return (await response.json()) as UploadAttachResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default { fetchToken, createApp, postNewStatus, fetchAccountInfo, uploadAttachment };
|
const fetchBookmarks = async (): Promise<BookmarkedStatus[]> => {
|
||||||
|
const { bookmarkLimit } = getPreferenceValues<Preference>();
|
||||||
|
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 };
|
||||||
|
|
65
src/bookmark.tsx
Normal file
65
src/bookmark.tsx
Normal file
|
@ -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<BookmarkedStatus[]>(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 + ``, "");
|
||||||
|
|
||||||
|
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("<br>" + content) + parsedImages;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List isShowingDetail isLoading={isLoading} searchBarPlaceholder="Search bookmarks">
|
||||||
|
{bookmarks?.map((bookmark) => (
|
||||||
|
<List.Item
|
||||||
|
title={bookmark.pleroma.content["text/plain"]}
|
||||||
|
key={bookmark.id}
|
||||||
|
detail={<List.Item.Detail markdown={parseStatus(bookmark)} />}
|
||||||
|
actions={
|
||||||
|
<ActionPanel>
|
||||||
|
<Action.OpenInBrowser title="Open Original Status" url={bookmark.url} />
|
||||||
|
</ActionPanel>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
}
|
26
src/types.ts
26
src/types.ts
|
@ -5,6 +5,7 @@ export type VisibilityScope = "public" | "unlisted" | "direct" | "private" | "lo
|
||||||
export interface Preference {
|
export interface Preference {
|
||||||
instance: string;
|
instance: string;
|
||||||
defaultVisibility: VisibilityScope;
|
defaultVisibility: VisibilityScope;
|
||||||
|
bookmarkLimit: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VisibilityOption {
|
export interface VisibilityOption {
|
||||||
|
@ -60,6 +61,23 @@ export interface Status {
|
||||||
to?: string[];
|
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
|
// API Responses
|
||||||
export interface ApiResponse {
|
export interface ApiResponse {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -90,8 +108,16 @@ export interface StatusAttachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadAttachResponse {
|
export interface UploadAttachResponse {
|
||||||
|
blurhash: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
id: string;
|
id: string;
|
||||||
|
meta: {
|
||||||
|
original: {
|
||||||
|
aspect: number;
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
pleroma: {
|
pleroma: {
|
||||||
mime_type: string;
|
mime_type: string;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue