Merge branch 'develop' into 'refactor/kernel-index-cc'

# Conflicts:
#   packages/backend/src/remote/activitypub/kernel/index.ts
This commit is contained in:
Kainoa Kanter 2023-09-02 16:59:32 +00:00
commit cb3f0b2ada
463 changed files with 14572 additions and 3580 deletions

3
.gitignore vendored
View file

@ -32,6 +32,9 @@ coverage
# docker dev config
/dev/docker-compose.yml
# ESLint
.eslintcache
# misskey
built
db

View file

@ -1,10 +1,18 @@
{
"recommendations": [
"editorconfig.editorconfig",
"rome.rome",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"vue.volar",
"vue.vscode-typescript-vue-plugin",
"arcanis.vscode-zipfs",
"Orta.vscode-twoslash-queries"
"orta.vscode-twoslash-queries",
"antfu.iconify",
"vivaxy.vscode-conventional-commits",
"ms-azuretools.vscode-docker",
"gitlab.gitlab-workflow",
"mrmlnc.vscode-json5",
"esbenp.prettier-vscode",
"redhat.vscode-yaml",
"yoavbls.pretty-ts-errors",
"biomejs.biome"
]
}

View file

@ -97,7 +97,7 @@ If you have access to a server that supports one of the sources below, I recomme
- 🐢 At least [NodeJS](https://nodejs.org/en/) v18.16.0 (v20 recommended)
- 🐘 At least [PostgreSQL](https://www.postgresql.org/) v12 (v14 recommended)
- 🍱 At least [Redis](https://redis.io/) v6 (v7 recommended)
- 🍱 At least [Redis](https://redis.io/) v7
- Web Proxy (one of the following)
- 🍀 Nginx (recommended)
- 🦦 Caddy

12
biome.json Normal file
View file

@ -0,0 +1,12 @@
{
"$schema": "https://biomejs.dev/schemas/1.0.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}

View file

@ -61,6 +61,8 @@ services:
# sonic:
# restart: unless-stopped
# image: docker.io/valeriansaliou/sonic:v1.4.0
# logging:
# driver: none
# networks:
# - calcnet
# volumes:

View file

@ -71,7 +71,7 @@ driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{n
\ ? Il sera retiré de toutes les publications qui le contiennent comme pièce-jointe."
unfollowConfirm: "Désirez-vous vous désabonner de {name} ?"
exportRequested: "Vous avez demandé une exportation. Lopération pourrait prendre
un peu de temps. Une terminée, le fichier résultant sera ajouté au Drive."
un peu de temps. Une fois terminée, le fichier résultant sera ajouté au Drive."
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes"
noLists: "Vous navez aucune liste"

View file

@ -74,7 +74,7 @@ exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Sete
importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat."
lists: "Daftar"
noLists: "Kamu tidak memiliki daftar apapun"
note: "Postingan"
note: "Posting"
notes: "Postingan"
following: "Ikuti"
followers: "Pengikut"
@ -1837,7 +1837,7 @@ _notification:
followBack: "Ikuti Kembali"
reply: "Balas"
renote: "Posting ulang"
reacted: berekasi ke postinganmu
reacted: mereaksi postinganmu
renoted: memposting ulang postinganmu
voted: memilih di angketmu
_deck:

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,6 @@
_lang_: "한국어"
headlineFirefish: "노트로 연결되는 네트워크"
introFirefish: "환영합니다! Firefish 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고
있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n
새로운 세계를 탐험해 보세요🚀"
headlineFirefish: "영원히 무료로 제공되는 오픈 소스 탈중앙화 소셜 미디어 플랫폼 🚀"
introFirefish: "환영합니다! Firefish 는 영원히 무료로 제공되는 오픈 소스 분산형 소셜 미디어 플랫폼입니다! 🚀"
monthAndDay: "{month}월 {day}일"
search: "검색"
notifications: "알림"
@ -14,8 +12,8 @@ ok: "OK"
gotIt: "알겠어요"
cancel: "취소"
enterUsername: "유저명 입력"
renotedBy: "{user}님이 Renote"
noNotes: "노트가 없습니다"
renotedBy: "{user}님이 부스트"
noNotes: "게시물이 없습니다"
noNotifications: "표시할 알림이 없습니다"
instance: "서버"
settings: "설정"
@ -45,7 +43,7 @@ copyContent: "내용 복사"
copyLink: "링크 복사"
delete: "삭제"
deleteAndEdit: "삭제 후 편집"
deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니까? 이 노트에 대한 리액션, 리노트, 답글 또한 모두 삭제됩니다."
deleteAndEditConfirm: "이 게시물을 삭제한 뒤 다시 편집하시겠습니까? 이 게시물에 대한 리액션, 부스트, 답글 또한 모두 삭제됩니다."
addToList: "리스트에 추가"
sendMessage: "메시지 보내기"
copyUsername: "유저명 복사"
@ -59,20 +57,20 @@ receiveFollowRequest: "새로운 팔로우 요청이 있습니다"
followRequestAccepted: "팔로우가 수락되었습니다"
mention: "멘션"
mentions: "받은 멘션"
directNotes: "다이렉트 노트"
directNotes: "다이렉트 게시물"
importAndExport: "가져오기와 내보내기"
import: "가져오기"
export: "내보내기"
files: "파일"
download: "다운로드"
driveFileDeleteConfirm: "파일 \"{name}\" 을 삭제하시겠습니까? 이 파일이 첨부되었더 노트에서도 같이 삭제됩니다."
driveFileDeleteConfirm: "파일 \"{name}\" 을 삭제하시겠습니까? 이 파일이 첨부되었더 게시물에서도 같이 삭제됩니다."
unfollowConfirm: "{name}님을 언팔로우하시겠습니까?"
exportRequested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 \"드라이브\"에 추가됩니다."
importRequested: "가져오기를 요청하였습니다. 이 작업에는 시간이 걸릴 수 있습니다."
lists: "리스트"
noLists: "리스트가 없습니다"
note: "노트"
notes: "노트"
note: "게시"
notes: "게시물"
following: "팔로잉"
followers: "팔로워"
followsYou: "당신을 팔로우합니다"
@ -96,13 +94,13 @@ followRequests: "팔로우 요청"
unfollow: "팔로우 해제"
followRequestPending: "팔로우 허가 대기중"
enterEmoji: "이모지 입력"
renote: "Renote"
unrenote: "Renote 취소"
renoted: "Renote 하였습니다"
cantRenote: "이 게시물은 Renote할 수 없습니다."
cantReRenote: "Renote를 Renote할 수 없습니다."
renote: "부스트"
unrenote: "부스트 취소"
renoted: "부스트 하였습니다"
cantRenote: "이 게시물은 부스트할 수 없습니다."
cantReRenote: "부스트를 부스트할 수 없습니다."
quote: "인용"
pinnedNote: "고정해놓은 노트"
pinnedNote: "고정해놓은 게시물"
pinned: "프로필에 고정"
you: "당신"
clickToShow: "클릭하여 보기"
@ -146,7 +144,7 @@ flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우
봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
flagAsCat: "나는 고양이다냥"
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
flagShowTimelineReplies: "타임라인에 게시물의 답글을 표시하기"
flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다."
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
addAccount: "계정 추가"
@ -205,7 +203,7 @@ noUsers: "아무도 없습니다"
editProfile: "프로필 수정"
noteDeleteConfirm: "이 게시물을 삭제하시겠습니까?"
pinLimitExceeded: "더 이상 고정할 수 없습니다."
intro: "Misskey의 설치가 완료되었습니다! 관리자 계정을 생성해주세요."
intro: "Firefish의 설치가 완료되었습니다! 관리자 계정을 생성해주세요."
done: "완료"
processing: "처리중"
preview: "미리보기"
@ -294,7 +292,7 @@ emptyDrive: "드라이브가 비어 있습니다"
emptyFolder: "폴더가 비어 있습니다"
unableToDelete: "삭제할 수 없습니다"
inputNewFileName: "바꿀 파일명을 입력해 주세요"
inputNewDescription: "새 캡션을 입력해 주세요"
inputNewDescription: "새 설명을 입력해 주세요"
inputNewFolderName: "바꿀 폴더명을 입력해 주세요"
circularReferenceFolder: "지정한 폴더가 이동할 폴더의 하위 폴더입니다."
hasChildFilesOrFolders: "이 폴더는 비어있지 않기 때문에 삭제할 수 없습니다."
@ -386,7 +384,7 @@ exploreFediverse: "연합우주를 탐색"
popularTags: "인기 태그"
userList: "리스트"
about: "정보"
aboutFirefish: "Misskey에 대하여"
aboutFirefish: "Firefish에 대하여"
administrator: "관리자"
token: "토큰"
twoStepAuthentication: "2단계 인증"
@ -537,8 +535,8 @@ sort: "정렬"
ascendingOrder: "오름차순"
descendingOrder: "내림차순"
scratchpad: "스크래치 패드"
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Firefish 와 상호 작용하는 코드를 작성,
실행 및 결과를 확인할 수 있습니다."
scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Firefish 와 상호 작용하는 코드를
작성, 실행 및 결과를 확인할 수 있습니다."
output: "출력"
script: "스크립트"
disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음"
@ -572,8 +570,8 @@ disablePlayer: "플레이어 닫기"
expandTweet: "트윗 확장하기"
themeEditor: "테마 에디터"
description: "설명"
describeFile: "캡션 추가"
enterFileDescription: "캡션 입력"
describeFile: "설명 추가"
enterFileDescription: "설명 입력"
author: "작성자"
leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?"
manage: "관리"
@ -703,7 +701,7 @@ experimentalFeatures: "실험실"
developer: "개발자"
makeExplorable: "\"발견하기\"에 내 계정 보이기"
makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시"
showGapBetweenNotesInTimeline: "타임라인의 게시물 사이를 띄워서 표시"
duplicate: "복제"
left: "왼쪽"
center: "가운데"
@ -758,7 +756,7 @@ unlikeConfirm: "좋아요를 취소할까요?"
fullView: "전체 화면"
quitFullView: "전체 화면 해제"
addDescription: "설명 추가"
userPagePinTip: "각 게시물의 메뉴에서 「프로필에 고정」을 선택하는 것으로, 여기에 노트를 표시해 둘 수 있어요."
userPagePinTip: "각 게시물의 메뉴에서 「프로필에 고정」을 선택하는 것으로, 여기에 게시물을 표시해 둘 수 있어요."
notSpecifiedMentionWarning: "수신자가 선택되지 않은 멘션이 있어요"
info: "정보"
userInfo: "유저 정보"
@ -789,7 +787,7 @@ gallery: "갤러리"
recentPosts: "최근 포스트"
popularPosts: "인기 포스트"
shareWithNote: "게시물로 공유"
ads: "광고"
ads: "커뮤니티 배너"
expiration: "기한"
memo: "메모"
priority: "우선순위"
@ -811,7 +809,7 @@ hashtags: "해시태그"
troubleshooting: "문제 해결"
useBlurEffect: "UI에 흐림 효과 사용"
learnMore: "자세히"
misskeyUpdated: "Misskey가 업데이트 되었습니다!"
misskeyUpdated: "Firefish가 업데이트 되었습니다!"
whatIsNew: "패치 정보 보기"
translate: "번역"
translatedFrom: "{x}에서 번역"
@ -840,7 +838,7 @@ unmuteThread: "글타래 뮤트 해제"
ffVisibility: "내 인맥의 공개 범위"
ffVisibilityDescription: "나의 팔로우와 팔로워 정보에 대한 공개 범위를 설정할 수 있습니다."
continueThread: "이 글타래 이어서 보기"
deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? "
deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까?"
incorrectPassword: "비밀번호가 올바르지 않습니다."
voteConfirm: "\"{choice}\"에 투표하시겠습니까?"
hide: "숨기기"
@ -992,12 +990,12 @@ _registry:
domain: "도메인"
createKey: "키 생성"
_aboutFirefish:
about: "Misskey는 syuilo에 의해서 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
about: "Firefish는 ThatOneCalculator에 의해서 2022년부터 개발되어 온 Misskey의 포크 소프트웨어 입니다."
contributors: "주요 기여자"
allContributors: "모든 기여자"
source: "소스 코드"
translation: "Misskey를 번역하기"
donate: "Misskey에 기부하기"
translation: "Firefish를 번역하기"
donate: "Firefish에 기부하기"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자"
patronsList: 기부 금액이 아닌 시간 순서로 정렬합니다. 위 링크를 통해 후원하여 당신의 이름을 새겨 보세요!
@ -1006,15 +1004,16 @@ _aboutFirefish:
pleaseDonateToFirefish: Firefish의 개발에 후원하는 것을 검토하여 주십시오.
donateHost: '{host} 에게 기부하기'
donateTitle: Firefish가 마음에 드시나요?
misskeyContributors: 오리지널 Misskey 기여자
_nsfw:
respect: "열람주의로 설정된 미디어 숨기기"
ignore: "열람 주의 미디어 항상 표시"
force: "미디어 항상 숨기기"
_mfm:
cheatSheet: "MFM 도움말"
intro: "MFM는 Misskey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할
수 있습니다."
dummy: "Misskey로 연합우주의 세계가 펼쳐집니다"
intro: "MFM는 Misskey나 Firefish, Akkoma 외의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서
사용할 수 있는 구문을 확인할 수 있습니다."
dummy: "Firefish로 연합우주의 세계가 펼쳐집니다"
mention: "멘션"
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
hashtag: "해시태그"
@ -1134,7 +1133,7 @@ _wordMute:
_instanceMute:
instanceMuteDescription: "뮤트한 서버에서 오는 답글을 포함한 모든 게시물과 부스트를 뮤트합니다."
instanceMuteDescription2: "한 줄에 하나씩 입력해 주세요"
title: "지정한 서버의 노트를 숨깁니다."
title: "지정한 서버의 게시물을 숨깁니다."
heading: "뮤트할 서버"
_theme:
explore: "테마 찾아보기"
@ -1747,7 +1746,7 @@ _notification:
youGotMention: "{name}님이 멘션함"
youGotReply: "{name}님이 답글함"
youGotQuote: "{name}님이 인용함"
youRenoted: "{name}님이 Boost"
youRenoted: "{name}님의 부스트"
youGotPoll: "{name}님이 투표함"
youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
@ -1824,7 +1823,7 @@ replayTutorial: 튜토리얼 다시 보기
renoteMute: 부스트 뮤트
antennaInstancesDescription: 서버 호스트를 한 줄에 하나씩 입력하세요
userSaysSomethingReason: '{name} 님이 {reason}에 대해 말했습니다'
userSaysSomethingReasonQuote: '{name} 님이 {reason} 을 포함하는 노트를 인용했습니다'
userSaysSomethingReasonQuote: '{name} 님이 {reason} 을 포함하는 게시물을 인용했습니다'
pushNotification: 푸시 알림
channelFederationWarn: 현재 채널은 다른 서버로 연합되지 않습니다
enableServerMachineStats: 서버의 머신 정보를 공개
@ -1836,7 +1835,7 @@ cannotUploadBecauseExceedsFileSizeLimit: 파일 크기 제한을 초과하여
pushNotificationNotSupported: 브라우저 및 서버가 푸시 알림을 지원하지 않습니다
enableRecommendedTimeline: 추천 타임라인을 활성화
pushNotificationAlreadySubscribed: 푸시 알림이 활성화되었습니다
caption: 자동 캡션
caption: 자동으로 설명 붙이기
findOtherInstance: 다른 서버 둘러보기
enableIdenticonGeneration: 유저 별 Identicon의 생성을 활성화
secureModeInfo: 인증 정보가 없는 리모트 서버로부터의 요청에 응답하지 않습니다.
@ -1858,7 +1857,7 @@ customKaTeXMacroDescription: 'KaTeX 매크로를 지정하여 수식을 더욱
사용할 수 없습니다. 올바르지 않은 정의는 무시됩니다. 문자열을 치환하는 수준에서만 지원하며, 조건 분기와 같은 고도의 구문은 사용할 수 없습니다.'
reactionPickerSkinTone: 선호하는 이모지 피부 톤
selectInstance: 서버 선택
showAds: 광고 보이기
showAds: 커뮤니티 배너를 보이기
searchPlaceholder: Firefish에서 검색
addInstance: 서버 추가
listsDesc: 리스트를 사용하여 특정 유저로 이루어진 타임라인을 구성할 수 있습니다. 리스트는 '타임라인' 페이지에서 접근할 수 있습니다.
@ -1867,7 +1866,7 @@ showEmojisInReactionNotifications: 리액션 알림에 이모지 보이기
hiddenTagsDescription: 트렌드와 '발견하기'에서 제외할 해시태그를 ('#'을 제외하고) 한 줄에 하나씩 입력하여 주십시오. 이 설정은
트렌드와 '발견하기' 외에는 영향을 주지 않습니다.
antennasDesc: "안테나에서는 조건에 맞는 게시물이 표시됩니다.\n'타임라인' 페이지에서 접근할 수 있습니다."
expandOnNoteClick: 노트를 클릭하여 자세히 표시
expandOnNoteClick: 게시물을 클릭하여 자세히 표시
expandOnNoteClickDesc: 비활성화한 경우에도 우클릭 메뉴 또는 타임스탬프를 클릭하여 열 수 있습니다.
customMOTDDescription: 유저가 페이지를 로딩/새로고침할 때 마다 무작위로 표시할 메시지를 한 줄에 하나씩 입력합니다.
moveFrom: 다른 계정에서 이 계정으로 이사하기
@ -1887,7 +1886,7 @@ swipeOnDesktop: 데스크톱에서도 모바일과 같은 스와이프를 사용
migration: 계정 이사
moveTo: 이 계정에서 새로운 계정으로 이사
deleted: 삭제됨
editNote: 노트 편집
editNote: 게시물 편집
edited: '편짐됨: {date} {time}'
customMOTD: 사용자 지정 MOTD (스플래시 화면 메시지)
selectChannel: 채널 선택
@ -1896,14 +1895,14 @@ splash: 스플래시 화면
preventAiLearningDescription: 업로드한 게시물이나 미디어를 AI 모델이 학습하지 말기를 요구합니다.
isBot: 이 계정은 봇입니다
isAdmin: 관리자
newer: 새로운 노트
older: 이전 노트
newer: 새로운 게시물
older: 이전 게시물
renoteUnmute: 부스트 뮤트 해제
accountMoved: '이 유저는 다른 계정으로 이사했습니다:'
silencedInstances: 사일런스한 서버
accessibility: 접근성
userSaysSomethingReasonReply: '{name} 님이 {reason} 을 포함하는 노트에 답글했습니다'
userSaysSomethingReasonRenote: '{name} 님이 {reason} 을 포함하는 노트를 부스트했습니다'
userSaysSomethingReasonReply: '{name} 님이 {reason} 을 포함하는 게시물에 답글했습니다'
userSaysSomethingReasonRenote: '{name} 님이 {reason} 을 포함하는 게시물을 부스트했습니다'
breakFollowConfirm: 팔로워를 해제하시겠습니까?
indexFrom: 이 게시물 ID부터 인덱싱하기
noThankYou: 괜찮습니다
@ -1949,8 +1948,8 @@ silencedWarning: 관리자가 사일런스한 서버에 속한 유저이며, 스
isModerator: 모더레이터
isPatron: Firefish 후원자
_experiments:
postImportsCaption: 유저가 과거에 작성한 게시물을 Firefish(Firefish), Misskey, Mastodon, Akkoma,
Pleroma 등에서 가져올 수 있게 합니다. 작업 대기열의 처리 속도가 느릴 경우 서비스에 영향이 갈 수 있습니다.
postImportsCaption: 유저가 과거에 작성한 게시물을 Firefish, Misskey, Mastodon, Akkoma, Pleroma
등에서 가져올 수 있게 합니다. 작업 대기열의 처리 속도가 느릴 경우 서비스에 영향이 갈 수 있습니다.
enablePostImports: 게시물 가져오기를 활성화
title: 실험실
_messaging:
@ -1958,8 +1957,8 @@ _messaging:
dms: 개인 메시지
_tutorial:
title: Firefly의 사용 방법
step5_5: '{icon} 소셜 타임라인은 홈 타임라인과 소셜 타임라인을 합친 것과 같습니다.'
step4_1: 노트를 올려 봅시다.
step5_5: '{icon} 소셜 타임라인은 홈 타임라인과 로컬 타임라인을 합친 것과 같습니다.'
step4_1: 글을 올려 봅시다.
step5_3: '{icon} 홈 타임라인은 내가 팔로우하고 있는 계정의 게시물을 볼 수 있는 타임라인입니다.'
step6_2: 이 서버에 가입을 마친 당신은 단순히 Firefish 서버의 유저가 아닌, 수많은 서버가 서로 상호작용하는 연합우주에 참가하시게
된 것입니다.
@ -2003,3 +2002,17 @@ _feeds:
_dialog:
charactersExceeded: 글자 수 제한을 초과했습니다! 현재 {current}자 / 최대 {max}자
charactersBelow: 최소 글자 수 보다 작습니다! 현재 {current}자 / 최소 {max}자
emojiPackCreator: 이모지 팩 만든이
objectStorageS3ForcePathStyleDesc: Endpoint URL을 '<bucket>.s3.amazonaws.com'가 아닌 's3.amazonaws.com/<bucket>/'와
같은 형식으로 사용할 경우에 활성화해 주세요.
confirm: 확인
importZip: ZIP으로 가져오기
exportZip: ZIP으로 내보내기
origin: 원본
objectStorageS3ForcePathStyle: 경로 기반 Endpoint URL을 사용하기
delete2fa: 2FA를 비활성화
delete2faConfirm: 이 계정에서 2FA를 영구히 삭제합니다. 계속하시겠습니까?
deletePasskeys: 보안 키 삭제
deletePasskeysConfirm: 이 계정에서 모든 보안 키를 영구히 삭제합니다. 계속하시겠습니까?
inputNotMatch: 입력이 일치하지 않습니다
addRe: 열람주의로 표시된 게시물의 답장에 're:' 붙이기

View file

@ -1,7 +1,7 @@
---
_lang_: "Português"
headlineFirefish: "Uma rede ligada por notas"
introFirefish: "Bem-vindo! Firefish é um serviço de microblogue descentralizado de código aberto.\nCria \"notas\" e partilha o que te ocorre com todos à tua volta. 📡\nCom \"reações\" podes também expressar logo o que sentes às notas de todos. 👍\nExploremos um novo mundo! 🚀"
introFirefish: "Bem-vindo! Firefish é um serviço de microblogue descentralizado de
código aberto, gratuito para sempre! 🚀"
monthAndDay: "{day}/{month}"
search: "Buscar"
notifications: "Notificações"
@ -44,7 +44,8 @@ copyContent: "Copiar conteúdos"
copyLink: "Copiar hiperligação"
delete: "Eliminar"
deleteAndEdit: "Eliminar e editar"
deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editá-la? Irás perder todas as suas reações, renotas e respostas."
deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editá-la?
Irás perder todas as suas reações, renotas e respostas."
addToList: "Adicionar a lista"
sendMessage: "Enviar uma mensagem"
copyUsername: "Copiar nome de utilizador"
@ -64,9 +65,11 @@ import: "Importar"
export: "Exportar"
files: "Ficheiros"
download: "Descarregar"
driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"? As notas que tenham este ficheiro anexado serão também apagadas."
driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"\
? As notas que tenham este ficheiro anexado serão também apagadas."
unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?"
exportRequested: "Pediste uma exportação. Este processo pode demorar algum tempo. Será adicionado à tua Drive após a conclusão do processo."
exportRequested: "Pediste uma exportação. Este processo pode demorar algum tempo.
Será adicionado à tua Drive após a conclusão do processo."
importRequested: "Pediste uma importação. Este processo pode demorar algum tempo."
lists: "Listas"
noLists: "Não tens nenhuma lista"
@ -81,9 +84,12 @@ error: "Erro"
somethingHappened: "Ocorreu um erro"
retry: "Tentar novamente"
pageLoadError: "Ocorreu um erro ao carregar a página."
pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente após algum tempo."
serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente novamente."
youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para atualizar seu cliente."
pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache
do browser. Experimenta limpar a cache e tenta novamente após algum tempo."
serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente
novamente."
youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para
atualizar seu cliente."
enterListName: "Insira um nome para a lista"
privacy: "Privacidade"
makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados"
@ -108,7 +114,8 @@ sensitive: "Conteúdo sensível"
add: "Adicionar"
reaction: "Reações"
reactionSetting: "Quais reações a mostrar no selecionador de reações"
reactionSettingDescription2: "Arraste para reordenar, clique para excluir, pressione + para adicionar."
reactionSettingDescription2: "Arraste para reordenar, clique para excluir, pressione
+ para adicionar."
rememberNoteVisibility: "Lembrar das configurações de visibilidade de notas"
attachCancel: "Remover anexo"
markAsSensitive: "Marcar como sensível"
@ -137,13 +144,18 @@ emojiUrl: "URL do Emoji"
addEmoji: "Adicionar um Emoji"
settingGuide: "Guia de configuração"
cacheRemoteFiles: "Memória transitória de arquivos remotos"
cacheRemoteFilesDescription: "Se você desabilitar essa configuração, os arquivos remotos não serão armazenados em memória transitória e serão vinculados diretamente. Economiza o armazenamento do servidor, mas não gera miniaturas, o que aumenta o tráfego."
cacheRemoteFilesDescription: "Se você desabilitar essa configuração, os arquivos remotos
não serão armazenados em memória transitória e serão vinculados diretamente. Economiza
o armazenamento do servidor, mas não gera miniaturas, o que aumenta o tráfego."
flagAsBot: "Marcar conta como robô"
flagAsBotDescription: "Se esta conta for operada por um programa, ative este sinalizador. Quando ativado, serve como um sinalizador para evitar o encadeamento de reações para outros programadores, e o manuseio do sistema do Firefish é adequado para bots."
flagAsBotDescription: "Se esta conta for operada por um programa, ative este sinalizador.
Quando ativado, serve como um sinalizador para evitar o encadeamento de reações
para outros programadores, e o manuseio do sistema do Firefish é adequado para bots."
flagAsCat: "Marcar conta como gato"
flagAsCatDescription: "Ative essa opção para marcar essa conta como gato."
flagShowTimelineReplies: "Mostrar respostas na linha de tempo"
flagShowTimelineRepliesDescription: "Quando ativado, a linha do tempo mostra as respostas às outras notas do utilizador, além da nota do utilizador."
flagShowTimelineRepliesDescription: "Quando ativado, a linha do tempo mostra as respostas
às outras notas do utilizador, além da nota do utilizador."
autoAcceptFollowed: "Aprove automaticamente os seguidores dos seguintes utilizadores"
addAccount: "Adicionar Conta"
loginFailed: "Não consegui logar"
@ -156,7 +168,10 @@ searchWith: "Buscar: {q}"
youHaveNoLists: "Não tem nenhuma lista"
followConfirm: "Tem certeza que quer deixar de seguir {name}?"
proxyAccount: "Conta proxy"
proxyAccountDescription: "Uma conta proxy é uma conta que atua como seguidora remota para utilizadores sob determinadas condições. Por exemplo, quando um utilizador lista um utilizador remoto, a atividade não será entregue à instância, a menos que alguém esteja seguindo o utilizador listado, portanto, a conta proxy deve seguir."
proxyAccountDescription: "Uma conta proxy é uma conta que atua como seguidora remota
para utilizadores sob determinadas condições. Por exemplo, quando um utilizador
lista um utilizador remoto, a atividade não será entregue à instância, a menos que
alguém esteja seguindo o utilizador listado, portanto, a conta proxy deve seguir."
host: "hospedeiro"
selectUser: "Selecionar utilizador"
recipient: "Morada"
@ -186,11 +201,15 @@ instanceInfo: "Informações da instância"
statistics: "Estatisticas"
clearQueue: "Limpar a fila"
clearQueueConfirmTitle: "Quer limpar a fila?"
clearQueueConfirmText: "Postagens não entregues não serão mais entregues. Normalmente você não precisa fazer isso."
clearQueueConfirmText: "Postagens não entregues não serão mais entregues. Normalmente
você não precisa fazer isso."
clearCachedFiles: "Limpar memória transitória"
clearCachedFilesConfirm: "Tem certeza de que deseja excluir todos os arquivos remotos armazenados em memória transitória?"
clearCachedFilesConfirm: "Tem certeza de que deseja excluir todos os arquivos remotos
armazenados em memória transitória?"
blockedInstances: "Instância bloqueada"
blockedInstancesDescription: "Defina os anfitriões das instâncias que deseja bloquear, separados por quebras de linha. Uma instância bloqueada não poderá interagir com esta instância."
blockedInstancesDescription: "Defina os anfitriões das instâncias que deseja bloquear,
separados por quebras de linha. Uma instância bloqueada não poderá interagir com
esta instância."
muteAndBlock: "Silenciar e bloquear"
mutedUsers: "Silenciar utilizador"
blockedUsers: "Utilizadores bloqueados"
@ -238,7 +257,9 @@ saved: "Salvo"
messaging: "Chat"
upload: "Enviando"
keepOriginalUploading: "Manter a imagem original"
keepOriginalUploadingDescription: "Mantenha a versão original ao carregar a imagem. Quando desligado, a imagem para publicação na web será gerada no navegador no momento do upload."
keepOriginalUploadingDescription: "Mantenha a versão original ao carregar a imagem.
Quando desligado, a imagem para publicação na web será gerada no navegador no momento
do upload."
fromDrive: "\nDa unidade"
fromUrl: "Da URL"
uploadFromUrl: "Carregamento de URL"
@ -262,8 +283,8 @@ yearsOld: "{age} anos"
registeredDate: "Data de registro"
location: "Lugar, colocar"
theme: "tema"
themeForLightMode: "Temas usados no modo de luz"
themeForDarkMode: "Temas usados no modo escuro"
themeForLightMode: "Tema a usar no Modo Diurno"
themeForDarkMode: "Temas usados no Modo Noturno"
light: "Claro"
dark: "Escuro"
lightThemes: "Tema claro"
@ -271,7 +292,7 @@ darkThemes: "Tema escuro"
syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo"
drive: "Unidades"
fileName: "Nome do Ficheiro"
selectFile: "Selecione os arquivos"
selectFile: "Selecione o arquivo"
selectFiles: "Selecione os arquivos"
selectFolder: "Selecionar uma pasta"
selectFolders: "Selecionar uma pasta"
@ -286,8 +307,9 @@ emptyFolder: "A pasta está vazia"
unableToDelete: "Não é possível eliminar"
inputNewFileName: "Por favor, digite um novo nome para a pasta!"
inputNewDescription: "Insira uma nova legenda"
inputNewFolderName: "Por favor, digite um novo nome para a pasta!"
circularReferenceFolder: "A pasta de destino é uma subpasta da pasta que você deseja mover."
inputNewFolderName: "Por favor, digite um novo nome para a pasta"
circularReferenceFolder: "A pasta de destino é uma subpasta da pasta que você deseja
mover."
hasChildFilesOrFolders: "Esta pasta não está vazia e não pode ser excluída."
copyUrl: "Copiar URL"
rename: "Renomear"
@ -321,7 +343,8 @@ connectService: "Conectar"
disconnectService: "Desconectar"
enableLocalTimeline: "Ativar linha do tempo local"
enableGlobalTimeline: "Ativar linha do tempo global"
disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores e moderadores ainda poderão usá-las por conveniência."
disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores
e moderadores ainda poderão usá-las por conveniência."
registration: "Registar"
enableRegistration: "Permitir que qualquer pessoa se registre"
invite: "Convidar"
@ -333,9 +356,11 @@ bannerUrl: "URL da imagem do banner"
backgroundImageUrl: "URL da imagem de fundo"
basicInfo: "Informações básicas"
pinnedUsers: "Utilizador fixado"
pinnedUsersDescription: "Descreva os utilizadores que você deseja fixar na página \"Localizar\", etc., separados por quebras de linha."
pinnedUsersDescription: "Descreva os utilizadores que você deseja fixar na página
\"Localizar\", etc., separados por quebras de linha."
pinnedPages: "Página fixada"
pinnedPagesDescription: "Descreva o caminho da página que você deseja fixar na página superior da instância, separada por quebras de linha."
pinnedPagesDescription: "Descreva o caminho da página que você deseja fixar na página
superior da instância, separada por quebras de linha."
pinnedClipId: "ID do clipe a ser fixado"
pinnedNotes: "Post fixado"
hcaptcha: "hCaptcha"
@ -346,18 +371,21 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Habilitar reCAPTCHA"
recaptchaSiteKey: "Chave do sítio web"
recaptchaSecretKey: "Chave secreta"
avoidMultiCaptchaConfirm: "O uso de vários captchas pode causar interferência. Deseja desativar outros captchas? Você também pode cancelar e deixar vários captchas ativados."
avoidMultiCaptchaConfirm: "O uso de vários captchas pode causar interferência. Deseja
desativar outros captchas? Você também pode cancelar e deixar vários captchas ativados."
antennas: "Antenas"
manageAntennas: "Gestão de antena"
name: "Nome"
antennaSource: "Origem de entrada"
antennaKeywords: "Palavras-chave recebidas"
antennaExcludeKeywords: "Palavras-chave negativas"
antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação AND, e se você separá-lo com uma quebra de linha, será uma especificação OR."
antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação
AND, e se você separá-lo com uma quebra de linha, será uma especificação OR."
notifyAntenna: "Notificar novas notas"
withFileAntenna: "Apenas notas com arquivos anexados"
enableServiceworker: "Ative as notificações push para o seu navegador"
antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de linha"
antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de
linha"
caseSensitive: "Maiúsculas e minúsculas"
withReplies: "Incluindo resposta"
connectedTo: "Você está conectado à seguinte conta"
@ -433,15 +461,19 @@ showFeaturedNotesInTimeline: "Mostrar notas recomendadas na linha do tempo"
objectStorage: "Armazenamento de objetos"
useObjectStorage: "Usar armazenamento de objetos"
objectStorageBaseUrl: "URL base"
objectStorageBaseUrlDesc: "O URL usado para referência. Se você estiver usando um CDN ou Proxy, seu URL, S3:'https: // <bucket> .s3.amazonaws.com', GCS, etc .:'https://storage.googleapis.com/ <bucket>' ."
objectStorageBaseUrlDesc: "O URL usado para referência. Se você estiver usando um
CDN ou Proxy, seu URL, S3:'https: // <bucket> .s3.amazonaws.com', GCS, etc .:'https://storage.googleapis.com/
<bucket>' ."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Especifique o nome do bucket do serviço a ser usado."
objectStoragePrefix: "Prefixo"
objectStoragePrefixDesc: "Ele é armazenado neste diretório de prefixo."
objectStorageEndpoint: "Ponto final"
objectStorageEndpointDesc: "Especifique vazio para S3, caso contrário, especifique o ponto final para cada serviço. Especifique como'<host>'ou'<host>: <port>'."
objectStorageEndpointDesc: "Especifique vazio para S3, caso contrário, especifique
o ponto final para cada serviço. Especifique como'<host>'ou'<host>: <port>'."
objectStorageRegion: "Região"
objectStorageRegionDesc: "Especifique uma região como 'xx-east-1'. Caso seu serviço não tenha o conceito de região, ele deve estar vazio ou 'us-east-1'."
objectStorageRegionDesc: "Especifique uma região como 'xx-east-1'. Caso seu serviço
não tenha o conceito de região, ele deve estar vazio ou 'us-east-1'."
objectStorageUseSSL: "Usar SSL"
objectStorageUseSSLDesc: "Desative-o se não quiser usar https para conexões de API"
objectStorageUseProxy: "Usar proxy"
@ -449,7 +481,8 @@ objectStorageUseProxyDesc: "Se você não usa proxy para conexão de API, desati
objectStorageSetPublicRead: "Definir 'public-read' ao fazer o upload"
serverLogs: "Registro do servidor"
deleteAll: "Apagar Tudo"
showFixedPostForm: "Exibir o formulário de postagem na parte superior da linha do tempo"
showFixedPostForm: "Exibir o formulário de postagem na parte superior da linha do
tempo"
newNoteRecived: "Nova nota recebida"
sounds: "Sons"
listen: "Ouvir"
@ -618,7 +651,8 @@ _pages:
_dailyRannum:
arg1: "Valor mínimo"
arg2: "Valor máximo"
dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuário)"
dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia
para cada usuário)"
_dailyRandomPick:
arg1: "Listas"
seedRandom: "Aleatório (com semente)"
@ -634,7 +668,8 @@ _pages:
_seedRandomPick:
arg1: "Semente"
arg2: "Listas"
DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuário)"
DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia
para cada usuário)"
_DRPWPM:
arg1: "Lista de texto"
pick: "Escolhe a partir da lista"
@ -665,7 +700,8 @@ _pages:
_for:
arg1: "Número de repetições"
arg2: "Ação"
typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado é do tipo \"{actual}\"!"
typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado
é do tipo \"{actual}\"!"
thereIsEmptySlot: "O espaço {slot} está vazio!"
types:
string: "Texto"
@ -730,3 +766,5 @@ _deck:
list: "Listas"
mentions: "Menções"
direct: "Notas diretas"
editNote: Editar post
edited: Editado a {date} às {time}

View file

@ -2044,7 +2044,7 @@ cannotUploadBecauseExceedsFileSizeLimit: Этот файл не может бы
apps: Приложения
silenceThisInstance: Заглушить сервер
silencedInstances: Заглушенные серверы
editNote: Редактировать заметку
editNote: Редактировать пост
edited: 'Редактировано в {date} {time}'
deleted: Удалённое
removeReaction: Удалить вашу реакцию

View file

@ -1,22 +1,22 @@
---
_lang_: "ภาษาไทย"
headlineFirefish: "เชื่อมต่อเครือข่ายโดยโน้ต"
introFirefish: "ยินดีต้อนรับจ้าาา! Firefish เป็นบริการไมโครบล็อกโอเพ่นซอร์ส แบบการกระจายอำนาจ\nสร้าง \"โน้ต\" เพื่อแบ่งปันความคิดของคุณกับทุกคนรอบตัวคุณกันเถอะ 📡\nด้วยการ \"รีแอคชั่นผู้คน\" คุณยังสามารถแสดงความรู้สึกของคุณเกี่ยวกับบันทึกของทุกคนได้อย่างรวดเร็ว 👍\n\nแล้วมาท่องสำรวจโลกใบใหม่กันเถอะ! 🚀"
introFirefish: "ยินดีต้อนรับค่ะ/ครับ! Firefish เป็นแพลตฟอร์มโซเชียลมีเดียแบบโอเพ่นซอร์สที่มีการกระจายอำนาจซึ่งให้บริการฟรีตลอดไป!
🚀"
monthAndDay: "{เดือน}/{วัน}"
search: "ค้นหา"
notifications: "การเเจ้งเตือน"
username: "ชื่อผู้ใช้"
password: "รหัสผ่าน"
forgotPassword: "ลืมรหัสผ่าน?"
forgotPassword: "ลืมรหัสผ่านอ่ะ"
fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส"
ok: "ตกลง"
gotIt: "เข้าใจแล้ว !"
cancel: "ยกเลิก"
enterUsername: "ใส่ชื่อผู้ใช้"
renotedBy: "รีโน้ตโดย {ผู้ใช้}"
noNotes: "ไม่มีโน้ต"
renotedBy: "บูตเตอร์โดย {user}"
noNotes: "ไม่มีโพสต์"
noNotifications: "ไม่มีการแจ้งเตือน"
instance: "ตัวอย่าง"
instance: "เซิฟเวอร์"
settings: "การตั้งค่า"
basicSettings: "การตั้งค่าพื้นฐาน"
otherSettings: "การตั้งค่าอื่นๆ"
@ -44,7 +44,8 @@ copyContent: "คัดลอกเนื้อหา"
copyLink: "คัดลอกลิงก์"
delete: "ลบ"
deleteAndEdit: "ลบและแก้ไข"
deleteAndEditConfirm: "นายแน่ใจแล้วเหรอ? ว่าต้องการลบโน้ตนี้และแก้ไข คุณอาจจะสูญเสียการโต้ตอบ, โน้ต, และการตอบกลับทั้งหมดได้นะ"
deleteAndEditConfirm: "คุณแน่ใจแล้วเหรอว่าต้องการลบโพสต์นี้และแก้ไข? คุณอาจจะสูญเสียการโต้ตอบ,
โพสต์, และการตอบกลับทั้งหมดได้นะ"
addToList: "เพิ่มในลิสต์"
sendMessage: "ส่งข้อความ"
copyUsername: "คัดลอกชื่อผู้ใช้"
@ -58,30 +59,31 @@ receiveFollowRequest: "คำขอผู้ติดตามที่ได้
followRequestAccepted: "ผู้ติดตามได้ตอบรับคำขอร้องของคุณแล้ว"
mention: "กล่าวถึง"
mentions: "พูดถึง"
directNotes: "ไดเร็คโน้ต"
directNotes: "ไดเร็คข้อความ"
importAndExport: "นำเข้า / ส่งออก"
import: "การนำเข้า"
export: "การนำออก"
files: "ไฟล์"
download: "ดาวน์โหลด"
driveFileDeleteConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการลบไฟล์ \"{name}\" โน้ตย่อที่แนบมากับไฟล์นี้ก็จะถูกลบด้วยนะ"
unfollowConfirm: "นายแน่ใจแล้วหรอว่าต้องการเลิกติดตาม {name}?"
driveFileDeleteConfirm: "คุณแน่ใจแล้วหรอว่าต้องการลบไฟล์ \"{name}\"? โพสต์ย่อที่แนบมากับไฟล์นี้ก็จะถูกลบด้วยนะ"
unfollowConfirm: "คุณแน่ใจแล้วหรอว่าต้องการเลิกติดตาม {name}?"
exportRequested: "เมื่อคุณได้ร้องขอการส่งออก อาจจะต้องใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว"
importRequested: "เมื่อคุณได้ร้องขอการนำเข้า อาจจะต้องใช้เวลาสักครู่นะ"
lists: "รายการ"
noLists: "คุณไม่มีลิสต์ใดๆนะ"
note: "ตัวโน้ต"
notes: "หมายเหตุ"
note: "โพสต์"
notes: "โพสต์"
following: "กำลังติดตาม"
followers: "ผู้ติดตาม"
followsYou: "ติดตามคุณ"
createList: "สร้างลิสต์"
manageLists: "จัดการลิสต์"
error: "ผิดพลาด!"
error: "ผิดพลาด"
somethingHappened: "อุ๊ย ! มีอะไรบางอย่างผิดพลาด"
retry: "ลองใหม่อีกครั้ง"
pageLoadError: "เกิดข้อผิดพลาดในการโหลดหน้านี้"
pageLoadErrorDescription: "โดยปกติแล้วมักจะเกิดจากข้อผิดพลาดของเครือข่ายหรือแคชของเบราว์เซอร์ ลองล้างแคชแล้วลองใหม่อีกครั้งหลังจากรอสักครู่ "
pageLoadErrorDescription: "โดยปกติแล้วมักจะเกิดจากข้อผิดพลาดของเครือข่ายหรือแคชของเบราว์เซอร์
ลองล้างแคชแล้วลองใหม่อีกครั้งหลังจากรอสักครู่นะ"
serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง ได้โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้งนะ"
youShouldUpgradeClient: "หากต้องการดูหน้านี้ได้โปรดกรุณา รีเซ็ตเพื่ออัปเดตไคลเอ็นต์ของคุณนะ"
enterListName: "ใส่ชื่อสำหรับรายการลิสต์"
@ -89,18 +91,18 @@ privacy: "ความเป็นส่วนตัว"
makeFollowManuallyApprove: "ติดตามคำขอที่ต้องได้รับการอนุมัติ"
defaultNoteVisibility: "การมองเห็นที่เป็นค่าเริ่มต้น"
follow: "กำลังติดตาม"
followRequest: "ส่งคำขอติดตาม"
followRequest: "คำขอติดตาม"
followRequests: "ติดตามการร้องขอ"
unfollow: "เลิกติดตาม"
followRequestPending: "กำลังรอดำเนินการร้องขอติดตาม"
enterEmoji: "ใส่อีโมจิ"
renote: "รีโน้ต"
unrenote: "เลิกรีโน้ต"
renoted: "รีโน้ตเอาไว้"
cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตไว้ใหม่ได้นะ"
cantReRenote: "ไม่สามารถรีโน้ตเอาไว้ใหม่ได้นะ"
renote: "บูสต์"
unrenote: "เลิกบูสต์"
renoted: "บูสต์แล้ว"
cantRenote: "โพสต์นี้ไม่สามารถบูสต์ใหม่ได้"
cantReRenote: "ไม่สามารถบูสต์ไว้ใหม่ได้"
quote: "อ้างคำพูด"
pinnedNote: "โน้ตที่ปักหมุดเอาไว้"
pinnedNote: "โพสต์ที่ปักหมุดแล้ว"
pinned: "ปักหมุดไปยังโปรไฟล์"
you: "ตัวเอง"
clickToShow: "คลิกเพื่อแสดง"
@ -109,7 +111,7 @@ add: "เพิ่ม"
reaction: "รีแอคชั่น"
reactionSetting: "รีแอคชั่นไปยังแสดงผลในตัวเลือกการรีแอคชั่น"
reactionSettingDescription2: "กดลากเพื่อจัดลำดับใหม่ กดคลิกเพื่อลบ กด \"+\" เพื่อเพิ่ม"
rememberNoteVisibility: "จดจำการตั้งค่าการมองเห็นตัวโน้ต"
rememberNoteVisibility: "จดจำการตั้งค่าการมองเห็นโพสต์"
attachCancel: "ลบไฟล์ออกที่แนบมา"
markAsSensitive: "ทำเครื่องหมายว่าละเอียดอ่อน"
unmarkAsSensitive: "ยกเลิกทำเครื่องหมายเป็น NSFW"
@ -120,11 +122,11 @@ block: "บล็อค"
unblock: "เลิกปิดกั้น"
suspend: "ถูกระงับ"
unsuspend: "ยกเลิกระงับ"
blockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการบล็อกบัญชีนี้"
unblockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการปลดบล็อคบัญชีนี้"
suspendConfirm: "นายแน่ใจแล้วเหรอว่าต้องการระงับบัญชีนี้อ่ะ?"
unsuspendConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการยกเลิกการระงับบัญชีนี้"
selectList: "เลือกรายการ (Automatic Translation)"
blockConfirm: "คุณแน่ใจแล้วเหรอ ว่าต้องการบล็อกบัญชีนี้?"
unblockConfirm: "คุณแน่ใจแล้วเหรอ ว่าต้องการปลดบล็อคบัญชีนี้?"
suspendConfirm: "คุณแน่ใจแล้วเหรอว่าต้องการระงับบัญชีนี้อ่ะ?"
unsuspendConfirm: "คุณแน่ใจแล้วหรอว่าต้องการยกเลิกการระงับบัญชีนี้?"
selectList: "เลือกรายการ"
selectAntenna: "เลือกเสาอากาศ"
selectWidget: "เลือกวิดเจ็ต"
editWidgets: "แก้ไขวิดเจ็ต"
@ -137,13 +139,17 @@ emojiUrl: "อิโมจิ URL"
addEmoji: "แทรกอีโมจิ"
settingGuide: "การตั้งค่าที่แนะนำ"
cacheRemoteFiles: "แคชไฟล์ระยะไกล"
cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกล แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ"
cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากระยะไกลเซิฟเวอร์
แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน
เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ"
flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอท"
flagAsBotDescription: "การเปิดใช้งานตัวเลือกนี้หากบัญชีนี้ถูกควบคุมโดยนักเขียนโปรแกรม หรือ ถ้าหากเปิดใช้งาน มันจะทำหน้าที่เป็นแฟล็กสำหรับนักพัฒนารายอื่นๆ และเพื่อป้องกันการโต้ตอบแบบไม่มีที่สิ้นสุดกับบอทตัวอื่นๆ และยังสามารถปรับเปลี่ยนระบบภายในของ Firefish เพื่อปฏิบัติต่อบัญชีนี้เป็นบอท"
flagAsBotDescription: "การเปิดใช้งานตัวเลือกนี้หากบัญชีนี้ถูกควบคุมโดยนักเขียนโปรแกรม
หรือ ถ้าหากเปิดใช้งาน มันจะทำหน้าที่เป็นแฟล็กสำหรับนักพัฒนารายอื่นๆ และเพื่อป้องกันการโต้ตอบแบบไม่มีที่สิ้นสุดกับบอทตัวอื่นๆ
และยังสามารถปรับเปลี่ยนระบบภายในของ Firefish เพื่อปฏิบัติต่อบัญชีนี้เป็นบอท"
flagAsCat: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นแมว"
flagAsCatDescription: "การเปิดใช้งานตัวเลือกนี้เพื่อทำเครื่องหมายบอกว่าบัญชีนี้เป็นแมว"
flagAsCatDescription: "คุณจะได้รับหูแมวและพูดเหมือนแมวนะ!"
flagShowTimelineReplies: "แสดงตอบกลับ ในไทม์ไลน์"
flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโน้ตของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้"
flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโพสต์ของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้"
autoAcceptFollowed: "อนุมัติคำขอติดตามโดยอัตโนมัติทันที จากผู้ใช้งานที่คุณกำลังติดตาม"
addAccount: "เพิ่มบัญชี"
loginFailed: "การเข้าสู่ระบบไม่สำเร็จ"
@ -155,15 +161,17 @@ removeWallpaper: "นำวอลเปเปอร์ออก"
searchWith: "ค้นหา: {q}"
youHaveNoLists: "รายการนี้ว่างเปล่า"
followConfirm: "คุณแน่ใจแล้วหรอว่าต้องการที่จะติดตาม {name}?"
proxyAccount: "บัญชี พร็อกซี่"
proxyAccountDescription: "บัญชีพร็อกซี่ คือ บัญชีที่จะทำหน้าที่เป็นผู้ติดตามระยะไกลสำหรับผู้ใช้งานที่อยู่ภายใต้ด้วยเงื่อนไขบางอย่าง ยกตัวอย่าง เช่น เมื่อมีผู้ใช้งานนั้นได้เพิ่มผู้ใช้งานจากระยะไกลลงในรายการ แต่กิจกรรมของผู้ใช้ในระยะไกลนั้นจะไม่ถูกส่งไปยังอินสแตนซ์หากไม่มีผู้ใช้งานในพื้นที่ติดตามผู้ใช้รายนั้น ดังนั้นบัญชีพร็อกซีนี้จะติดตามแทน"
proxyAccount: "บัญชีพร็อกซี"
proxyAccountDescription: "บัญชีพร็อกซี่ คือ บัญชีที่จะทำหน้าที่เป็นผู้ติดตามระยะไกลสำหรับผู้ใช้งานที่อยู่ภายใต้ด้วยเงื่อนไขบางอย่าง
ยกตัวอย่าง เช่น เมื่อมีผู้ใช้งานนั้นได้เพิ่มผู้ใช้งานจากระยะไกลลงในรายการ แต่กิจกรรมของผู้ใช้ในระยะไกลนั้นจะไม่ถูกส่งไปยังเซิฟเวอร์
หากไม่มีผู้ใช้งานในพื้นที่ติดตามผู้ใช้รายนั้น ดังนั้นบัญชีพร็อกซีนี้จะติดตามแทน"
host: "โฮสต์"
selectUser: "เลือกผู้ใช้งาน"
recipient: "ผู้รับ"
annotation: "ความคิดเห็น"
federation: "สหพันธ์"
instances: "ตัวอย่าง"
registeredAt: "จดทะเบียนที่"
instances: "เซิฟเวอร์"
registeredAt: "จดทะเบียนแล้วที่"
latestRequestSentAt: "ส่งคำขอล่าสุดไปแล้ว"
latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว"
latestStatus: "สถานะล่าสุด"
@ -186,7 +194,8 @@ instanceInfo: "ข้อมูล อินสแตนซ์"
statistics: "สถิติการใช้งาน"
clearQueue: "ล้างคิว"
clearQueueConfirmTitle: "คุณแน่ใจแล้วหรอว่าต้องการที่จะล้างคิว?"
clearQueueConfirmText: "บันทึกย่อที่ยังไม่ได้ส่งที่เหลืออยู่ในคิวนั้นมักจะ ไม่ถูกรวมเข้าด้วยกัน โดยปกติแล้วไม่จำเป็นต้องดำเนินการนี้"
clearQueueConfirmText: "บันทึกย่อที่ยังไม่ได้ส่งที่เหลืออยู่ในคิวนั้นมักจะ ไม่ถูกรวมเข้าด้วยกัน
โดยปกติแล้วไม่จำเป็นต้องดำเนินการนี้"
clearCachedFiles: "ล้างแคช"
clearCachedFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ระยะไกลที่แคชไว้ทั้งหมด?"
blockedInstances: "อินสแตนซ์ที่ ถูกบล็อก"
@ -232,14 +241,15 @@ announcements: "ประกาศ"
imageUrl: "url รูปภาพ"
remove: "ลบ"
removed: "ถูกลบไปแล้ว"
removeAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\""
deleteAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\""
resetAreYouSure: "รีเซ็ตเลยไหม"
removeAreYouSure: "คุณแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\"?"
deleteAreYouSure: "คุณแน่ใจหรอว่าต้องการที่จะลบออก \"{x}\"?"
resetAreYouSure: "จริงหรอรีเซ็ตเลยไหม?"
saved: "บันทึกแล้ว"
messaging: "แชท"
upload: "อัพโหลด"
keepOriginalUploading: "เก็บภาพต้นฉบับ"
keepOriginalUploadingDescription: "บันทึกรูปภาพที่อัพโหลดต้นฉบับตามที่เป็นอยู่ ถ้าหากปิดอยู่ ระบบจะสร้างเวอร์ชั่นที่จะแสดงบนเว็บเมื่ออัพโหลดนะ"
keepOriginalUploadingDescription: "บันทึกรูปภาพที่อัพโหลดต้นฉบับตามที่เป็นอยู่ ถ้าหากปิดอยู่
ระบบจะสร้างเวอร์ชั่นที่จะแสดงบนเว็บเมื่ออัพโหลดนะ"
fromDrive: "จากไดรฟ์"
fromUrl: "จาก URL"
uploadFromUrl: "อัพโหลดจาก URL"
@ -253,7 +263,7 @@ startMessaging: "เริ่มการสนทนา"
nUsersRead: "อ่านโดย {n}"
agreeTo: "ฉันยอมรับที่จะ {0}"
tos: "ข้อกำหนดและเงื่อนไข"
start: "เริ่มต้น​ใช้งาน​"
start: "เริ่มต้น"
home: "หน้าแรก"
remoteUserCaution: "เนื่องจากผู้ใช้งานรายนี้นั้น มาจากอินสแตนซ์ระยะไกล ข้อมูลที่แสดงดังกล่าวนั้นอาจจะไม่สมบูรณ์ก็ได้นะ"
activity: "กิจกรรม"
@ -334,9 +344,11 @@ bannerUrl: "URL รูปภาพแบนเนอร์"
backgroundImageUrl: "URL ภาพพื้นหลัง"
basicInfo: "ข้อมูลเบื้องต้น"
pinnedUsers: "ผู้ใช้งานที่ได้รับการปักหมุด"
pinnedUsersDescription: "ลิสต์ชื่อผู้ใช้โดยคั่นด้วยการขึ้นบรรทัดใหม่เพื่อปักหมุดในแท็บ \"สำรวจ\""
pinnedUsersDescription: "ลิสต์ชื่อผู้ใช้โดยคั่นด้วยการขึ้นบรรทัดใหม่เพื่อปักหมุดในแท็บ
\"สำรวจ\""
pinnedPages: "หน้าที่ปักหมุด"
pinnedPagesDescription: "ป้อนเส้นทางของหน้าที่คุณต้องการตรึงไว้ที่หน้าแรกของอินสแตนซ์นี้ โดยคั่นด้วยตัวแบ่งบรรทัด"
pinnedPagesDescription: "ป้อนเส้นทางของหน้าที่คุณต้องการตรึงไว้ที่หน้าแรกของอินสแตนซ์นี้
โดยคั่นด้วยตัวแบ่งบรรทัด"
pinnedClipId: "ID ของคลิปที่จะปักหมุด"
pinnedNotes: "โน้ตที่ปักหมุดเอาไว้"
hcaptcha: "hCaptcha"
@ -347,14 +359,17 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "เปิดใช้ reCAPTCHA"
recaptchaSiteKey: "คีย์ไซต์"
recaptchaSecretKey: "คีย์ลับ"
avoidMultiCaptchaConfirm: "การใช้ระบบ Captcha หลายระบบอาจทำให้เกิดการรบกวนหรืออาจจะเกิดข้อผิดพลาดได้ หากต้องการที่จะปิดการใช้งานระบบ Captcha อื่น ๆ แนะนำให้ปิดตัวอื่นๆก่อน ถ้าหากคุณต้องการให้เปิดใช้งานต่อไป ให้ กด ยกเลิก"
avoidMultiCaptchaConfirm: "การใช้ระบบ Captcha หลายระบบอาจทำให้เกิดการรบกวนหรืออาจจะเกิดข้อผิดพลาดได้
หากต้องการที่จะปิดการใช้งานระบบ Captcha อื่น ๆ แนะนำให้ปิดตัวอื่นๆก่อน ถ้าหากคุณต้องการให้เปิดใช้งานต่อไป
ให้ กด ยกเลิก"
antennas: "เสาอากาศ"
manageAntennas: "จัดการเสาอากาศ"
name: "ชื่อ"
antennaSource: "แหล่งเสาอากาศ"
antennaKeywords: "คีย์เวิร์ดที่ควรฟัง"
antennaExcludeKeywords: "คีย์เวิร์ดที่จะยกเว้น"
antennaKeywordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR นะ"
antennaKeywordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข
OR นะ"
notifyAntenna: "แจ้งเตือนเกี่ยวกับโน้ตใหม่"
withFileAntenna: "เฉพาะโน้ตที่มีไฟล์"
enableServiceworker: "เปิดใช้งาน การแจ้งเตือนแบบพุชสำหรับเบราว์เซอร์ของคุณ"
@ -433,7 +448,8 @@ invitationCode: "รหัสคำเชิญ"
checking: "Checking"
available: "พร้อมใช้งาน"
unavailable: "ไม่พร้อมใช้"
usernameInvalidFormat: "คุณสามารถใช้อักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก ตัวเลข และขีดล่างได้นะ ( a-z , A-Z , 0-9 , รวมไปถึงอักษรพิเศษเช่น + * / , . - อื่นๆเป็นต้น )"
usernameInvalidFormat: "คุณสามารถใช้อักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก ตัวเลข และขีดล่างได้นะ
( a-z , A-Z , 0-9 , รวมไปถึงอักษรพิเศษเช่น + * / , . - อื่นๆเป็นต้น )"
tooShort: "สั้นเกินไปนะ"
tooLong: "ยาวเกินไปนะ"
weakPassword: "รหัสผ่าน แย่มาก"
@ -483,19 +499,26 @@ showFeaturedNotesInTimeline: "แสดงโน้ตเด่นในไท
objectStorage: "อ็อบเจ็กต์ ที่จัดเก็บ"
useObjectStorage: "ใช้ อ็อบเจ็กต์ ที่จัดเก็บ"
objectStorageBaseUrl: "URL ฐาน"
objectStorageBaseUrlDesc: "URL ที่ใช้เป็นข้อมูลอ้างอิง ระบุ URL ของ CDN หรือ Proxy ถ้าหากคุณใช้อย่างใดอย่างหนึ่ง\n สำหรับการใช้งาน S3 'https://<bucket>.s3.amazonaws.com' และสำหรับ GCS หรือบริการที่เทียบเท่าใช้ 'https://storage.googleapis.com/<bucket>', เป็นต้น"
objectStorageBaseUrlDesc: "URL ที่ใช้เป็นข้อมูลอ้างอิง ระบุ URL ของ CDN หรือ Proxy
ถ้าหากคุณใช้อย่างใดอย่างหนึ่ง\n สำหรับการใช้งาน S3 'https://<bucket>.s3.amazonaws.com'
และสำหรับ GCS หรือบริการที่เทียบเท่าใช้ 'https://storage.googleapis.com/<bucket>',
เป็นต้น"
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "โปรดระบุชื่อที่เก็บข้อมูลที่ใช้กับผู้ให้บริการของคุณ"
objectStoragePrefix: "คำนำหน้า"
objectStoragePrefixDesc: "ไฟล์ทั้งหมดจะถูกเก็บไว้ภายใต้ไดเร็กทอรีที่มีคำนำหน้านี้นะ"
objectStorageEndpoint: "ปลายทาง"
objectStorageEndpointDesc: "เว้นว่างไว้หากคุณใช้ AWS S3 หรือระบุปลายทางเป็น '<host>' หรือ '<host>:<port>' ทั้งนี้ขึ้นอยู่กับผู้ให้บริการที่คุณใช้อยู่ด้วย"
objectStorageEndpointDesc: "เว้นว่างไว้หากคุณใช้ AWS S3 หรือระบุปลายทางเป็น '<host>'
หรือ '<host>:<port>' ทั้งนี้ขึ้นอยู่กับผู้ให้บริการที่คุณใช้อยู่ด้วย"
objectStorageRegion: "ภูมิภาค"
objectStorageRegionDesc: "ระบุภูมิภาค เช่น 'xx-east-1' ถ้าหากบริการของคุณไม่ได้แยกความแตกต่างระหว่างภูมิภาคก็ให้ เว้นว่างไว้หรือป้อน 'us-east-1'"
objectStorageRegionDesc: "ระบุภูมิภาค เช่น 'xx-east-1' ถ้าหากบริการของคุณไม่ได้แยกความแตกต่างระหว่างภูมิภาคก็ให้
เว้นว่างไว้หรือป้อน 'us-east-1'"
objectStorageUseSSL: "ใช้ SSL"
objectStorageUseSSLDesc: "ปิดการทำงานนี้ไว้ ถ้าหากคุณจะไม่ใช้ HTTPS สำหรับการเชื่อมต่อ API"
objectStorageUseSSLDesc: "ปิดการทำงานนี้ไว้ ถ้าหากคุณจะไม่ใช้ HTTPS สำหรับการเชื่อมต่อ
API"
objectStorageUseProxy: "เชื่อมต่อผ่านพร็อกซี"
objectStorageUseProxyDesc: "ปิดสิ่งนี้ไว้ถ้าหากคุณจะไม่ใช้ Proxy สำหรับการเชื่อมต่อ API"
objectStorageUseProxyDesc: "ปิดสิ่งนี้ไว้ถ้าหากคุณจะไม่ใช้ Proxy สำหรับการเชื่อมต่อ
API"
objectStorageSetPublicRead: "ตั้งค่า \"public-read\" ในการอัปโหลด"
serverLogs: "บันทึกของเซิร์ฟเวอร์"
deleteAll: "ลบทั้งหมด"
@ -523,7 +546,8 @@ sort: "เรียงลำดับ"
ascendingOrder: "เรียงจากน้อยไปมาก"
descendingOrder: "เรียงจากมากไปน้อย"
scratchpad: "กระดานทดลอง"
scratchpadDescription: "Scratchpad เป็นการจัดเตรียมสภาพแวดล้อมสำหรับการทดลอง AiScript แต่คุณสามารถเขียน ดำเนินการ และตรวจสอบผลลัพธ์ของการโต้ตอบกับ Firefish มันได้ด้วยนะ"
scratchpadDescription: "Scratchpad เป็นการจัดเตรียมสภาพแวดล้อมสำหรับการทดลอง AiScript
แต่คุณสามารถเขียน ดำเนินการ และตรวจสอบผลลัพธ์ของการโต้ตอบกับ Firefish มันได้ด้วยนะ"
output: "เอาท์พุต"
script: "สคริปต์"
disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ"
@ -531,11 +555,14 @@ updateRemoteUser: "อัปเดตข้อมูลผู้ใช้งา
deleteAllFiles: "ลบไฟล์ทั้งหมด"
deleteAllFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ทั้งหมด?"
removeAllFollowing: "เลิกติดตามผู้ใช้ที่ติดตามทั้งหมด"
removeAllFollowingDescription: "การที่คุณดำเนินการนี้จะเลิกติดตามบัญชีทั้งหมดจาก {host} โปรดเรียกใช้คำสั่งสิ่งนี้หากต้องการยกเลิกอินสแตนซ์ เช่น ไม่มีอยู่แล้ว"
removeAllFollowingDescription: "การที่คุณดำเนินการนี้จะเลิกติดตามบัญชีทั้งหมดจาก {host}
โปรดเรียกใช้คำสั่งสิ่งนี้หากต้องการยกเลิกอินสแตนซ์ เช่น ไม่มีอยู่แล้ว"
userSuspended: "ผู้ใช้รายนี้ถูกระงับการใช้งาน"
userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น"
yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ"
yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่"
yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน
หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด
ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่"
menu: "เมนู"
divider: "ตัวแบ่ง"
addItem: "เพิ่มรายการ"
@ -589,13 +616,15 @@ smtpHost: "โฮสต์"
smtpPort: "พอร์ต"
smtpUser: "ชื่อผู้ใช้"
smtpPass: "รหัสผ่าน"
emptyToDisableSmtpAuth: "ปล่อยชื่อผู้ใช้และรหัสผ่านว่างไว้เพื่อปิดใช้งานการยืนยัน SMTP"
emptyToDisableSmtpAuth: "ปล่อยชื่อผู้ใช้และรหัสผ่านว่างไว้เพื่อปิดใช้งานการยืนยัน
SMTP"
smtpSecure: "ใช้โดยนัย SSL/TLS สำหรับการเชื่อมต่อ SMTP"
smtpSecureInfo: "ปิดสิ่งนี้เมื่อใช้ STARTTLS"
testEmail: "ทดสอบการส่งอีเมล"
wordMute: "ปิดเสียงคำ"
regexpError: "ข้อผิดพลาดของนิพจน์ทั่วไป"
regexpErrorDescription: "เกิดข้อผิดพลาดในนิพจน์ทั่วไปในบรรทัดที่ {line} ของการปิดเสียงคำ {tab} ของคุณ:"
regexpErrorDescription: "เกิดข้อผิดพลาดในนิพจน์ทั่วไปในบรรทัดที่ {line} ของการปิดเสียงคำ
{tab} ของคุณ:"
instanceMute: "ปิดเสียง อินสแตนซ์"
userSaysSomething: "{name} พูดอะไรบางอย่าง"
makeActive: "เปิดใช้งาน"
@ -611,10 +640,12 @@ create: "สร้าง"
notificationSetting: "ตั้งค่าการแจ้งเตือน"
notificationSettingDesc: "เลือกประเภทการแจ้งเตือนที่ต้องการจะแสดง"
useGlobalSetting: "ใช้การตั้งค่าส่วนกลาง"
useGlobalSettingDesc: "หากเปิดไว้ ระบบจะใช้การตั้งค่าการแจ้งเตือนของบัญชีของคุณ หากปิดอยู่ สามารถทำการกำหนดค่าแต่ละรายการได้นะ"
useGlobalSettingDesc: "หากเปิดไว้ ระบบจะใช้การตั้งค่าการแจ้งเตือนของบัญชีของคุณ หากปิดอยู่
สามารถทำการกำหนดค่าแต่ละรายการได้นะ"
other: "อื่น ๆ"
regenerateLoginToken: "สร้างโทเค็นการเข้าสู่ระบบอีกครั้ง"
regenerateLoginTokenDescription: "สร้างโทเค็นใหม่ที่ใช้ภายในระหว่างการเข้าสู่ระบบ โดยตามหลักปกติแล้วการดำเนินการนี้ไม่จำเป็น หากสร้างใหม่ อุปกรณ์ทั้งหมดจะถูกออกจากระบบนะ"
regenerateLoginTokenDescription: "สร้างโทเค็นใหม่ที่ใช้ภายในระหว่างการเข้าสู่ระบบ
โดยตามหลักปกติแล้วการดำเนินการนี้ไม่จำเป็น หากสร้างใหม่ อุปกรณ์ทั้งหมดจะถูกออกจากระบบนะ"
setMultipleBySeparatingWithSpace: "คั่นหลายรายการด้วยช่องว่าง"
fileIdOrUrl: "ไฟล์ ID หรือ URL"
behavior: "พฤติกรรม"
@ -622,7 +653,8 @@ sample: "ตัวอย่าง"
abuseReports: "รายงาน"
reportAbuse: "รายงาน"
reportAbuseOf: "รายงาน {ชื่อ}"
fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ ได้โปรดระบุ URL"
fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ
ได้โปรดระบุ URL"
abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ"
reporter: "นักข่าว"
reporteeOrigin: "รายงานต้นทาง"
@ -648,7 +680,8 @@ createNewClip: "สร้างคลิปใหม่"
unclip: "ลบคลิป"
confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป \"{name}\" แล้ว คุณต้องการลบออกจากคลิปนี้แทนอย่างงั้นหรอ?"
public: "สาธารณะ"
i18nInfo: "Firefish กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่ {link}"
i18nInfo: "Firefish กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่
{link}"
manageAccessTokens: "การจัดการโทเค็นการเข้าถึง"
accountInfo: "ข้อมูลบัญชี"
notesCount: "จำนวนของโน้ต"
@ -667,8 +700,10 @@ no: "ไม่"
driveFilesCount: "จำนวนไฟล์ไดรฟ์"
driveUsage: "การใช้พื้นที่ไดรฟ์"
noCrawle: "ปฏิเสธการจัดทำดัชนีของโปรแกรมรวบรวมข้อมูล"
noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ บันทึกย่อ หน้า ฯลฯ"
lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องตั้งค่าการเปิดเผยโน้ตเป็น \"ผู้ติดตามเท่านั้น\" โน้ตย่อของคุณจะปรากฏแก่ทุกคน ถึงแม้ว่าคุณจะเป็นกำหนดให้ผู้ติดตามต้องได้รับการอนุมัติด้วยตนเองก็ตาม"
noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ บันทึกย่อ หน้า
ฯลฯ"
lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องตั้งค่าการเปิดเผยโน้ตเป็น \"ผู้ติดตามเท่านั้น\"\
\ โน้ตย่อของคุณจะปรากฏแก่ทุกคน ถึงแม้ว่าคุณจะเป็นกำหนดให้ผู้ติดตามต้องได้รับการอนุมัติด้วยตนเองก็ตาม"
alwaysMarkSensitive: "ทำเครื่องหมายเป็น NSFW เป็นค่าเริ่มต้น"
loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ"
disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว"
@ -684,7 +719,8 @@ clips: "คลิป"
experimentalFeatures: "ฟังก์ชั่นทดสอบ"
developer: "สำหรับนักพัฒนา"
makeExplorable: "ทำให้บัญชีมองเห็นใน \"สำรวจ\""
makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน \"สำรวจ\" นะ"
makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน
\"สำรวจ\" นะ"
showGapBetweenNotesInTimeline: "แสดงช่องว่างระหว่างโพสต์บนไทม์ไลน์"
duplicate: "ทำซ้ำ"
left: "ซ้าย"
@ -699,7 +735,9 @@ onlineUsersCount: "{n} ผู้ใช้คนนี้กำลังออน
nUsers: "{n} ผู้ใช้งาน"
nNotes: "{n} โน้ต"
sendErrorReports: "ส่งรายงานว่าข้อผิดพลาด"
sendErrorReportsDescription: "เมื่อเปิดใช้งาน ข้อมูลข้อผิดพลาดโดยรายละเอียดนั้นจะถูกแชร์ให้กับ Firefish เมื่อเกิดปัญหา ซึ่งช่วยปรับปรุงคุณภาพของ Firefish\nซึ่งจะรวมถึงข้อมูล เช่น เวอร์ชั่นของระบบปฏิบัติการ เบราว์เซอร์ที่คุณใช้ กิจกรรมของคุณใน Firefish เป็นต้น"
sendErrorReportsDescription: "เมื่อเปิดใช้งาน ข้อมูลข้อผิดพลาดโดยรายละเอียดนั้นจะถูกแชร์ให้กับ
Firefish เมื่อเกิดปัญหา ซึ่งช่วยปรับปรุงคุณภาพของ Firefish\nซึ่งจะรวมถึงข้อมูล เช่น
เวอร์ชั่นของระบบปฏิบัติการ เบราว์เซอร์ที่คุณใช้ กิจกรรมของคุณใน Firefish เป็นต้น"
myTheme: "ธีมของฉัน"
backgroundColor: "ภาพพื้นหลัง"
accentColor: "รูปแบบสี"
@ -745,7 +783,8 @@ userInfo: "ข้อมูลผู้ใช้"
unknown: "ไม่ทราบสถานะ"
onlineStatus: "สถานะออนไลน์"
hideOnlineStatus: "ซ่อนสถานะออนไลน์"
hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์ของคุณช่วยลดความสะดวกของคุณสมบัติบางอย่าง เช่น การค้นหา อ่ะนะ"
hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์ของคุณช่วยลดความสะดวกของคุณสมบัติบางอย่าง
เช่น การค้นหา อ่ะนะ"
online: "ออนไลน์"
active: "ใช้งานอยู่"
offline: "ออฟไลน์"
@ -795,7 +834,8 @@ whatIsNew: "แสดงการเปลี่ยนแปลง"
translate: "แปลภาษา"
translatedFrom: "แปลมาจาก {x}"
accountDeletionInProgress: "กำลังดำเนินการลบบัญชีอยู่"
usernameInfo: "ชื่อที่ระบุบัญชีของคุณจากผู้อื่นในเซิร์ฟเวอร์นี้ คุณสามารถใช้ตัวอักษร (a~z, A~Z), ตัวเลข (0~9) หรือขีดล่าง (_) ชื่อผู้ใช้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง"
usernameInfo: "ชื่อที่ระบุบัญชีของคุณจากผู้อื่นในเซิร์ฟเวอร์นี้ คุณสามารถใช้ตัวอักษร
(a~z, A~Z), ตัวเลข (0~9) หรือขีดล่าง (_) ชื่อผู้ใช้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง"
aiChanMode: "โหมด Ai "
keepCw: "เก็บคำเตือนเนื้อหา"
pubSub: "บัญชีผับ/ย่อย"
@ -864,7 +904,8 @@ typeToConfirm: "โปรดป้อน {x} เพื่อยืนยัน"
deleteAccount: "ลบบัญชี"
document: "เอกสาร"
numberOfPageCache: "จำนวนหน้าเพจที่แคช"
numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย"
numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน
แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย"
logoutConfirm: "คุณแน่ใจว่าต้องการออกจากระบบ?"
lastActiveDate: "ใช้งานล่าสุดที่"
statusbar: "ไอคอนบนแถบสถานะ"
@ -881,22 +922,29 @@ sensitiveMediaDetection: "การตรวจจับของสื่อ NS
localOnly: "เฉพาะท้องถิ่น"
remoteOnly: "รีโมทเท่านั้น"
failedToUpload: "การอัปโหลดล้มเหลว"
cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น NSFW"
cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น
NSFW"
cannotUploadBecauseNoFreeSpace: "การอัปโหลดนั้นล้มเหลวเนื่องจากไม่มีความจุของไดรฟ์"
beta: "เบต้า"
enableAutoSensitive: "ทำเครื่องหมาย NSFW อัตโนมัติ"
enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่องหากเป็นไปได้ แม้ว่าตัวเลือกนี้จะถูกปิดใช้งาน แต่ก็สามารถเปิดใช้งานได้ทั้งอินสแตนซ์นี้"
activeEmailValidationDescription: "เปิดใช้งานการตรวจสอบที่อยู่อีเมลให้มีความเข้มงวดยิ่งขึ้น ซึ่งอาจจะรวมไปถึงการตรวจสอบที่อยู่อีเมล์ที่ใช้แล้วทิ้งและโดยให้พิจารณาว่าสามารถสื่อสารด้วยได้หรือไม่ เมื่อไม่เลือกระบบจะตรวจสอบเฉพาะรูปแบบของอีเมลเท่านั้น"
enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่องหากเป็นไปได้
แม้ว่าตัวเลือกนี้จะถูกปิดใช้งาน แต่ก็สามารถเปิดใช้งานได้ทั้งอินสแตนซ์นี้"
activeEmailValidationDescription: "เปิดใช้งานการตรวจสอบที่อยู่อีเมลให้มีความเข้มงวดยิ่งขึ้น
ซึ่งอาจจะรวมไปถึงการตรวจสอบที่อยู่อีเมล์ที่ใช้แล้วทิ้งและโดยให้พิจารณาว่าสามารถสื่อสารด้วยได้หรือไม่
เมื่อไม่เลือกระบบจะตรวจสอบเฉพาะรูปแบบของอีเมลเท่านั้น"
navbar: "แถบนำทาง"
shuffle: "สลับ"
account: "บัญชีผู้ใช้"
move: "ย้าย"
_sensitiveMediaDetection:
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง
การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
sensitivity: "การตรวจจับความไว"
sensitivityDescription: "การลดความไวนั้นจะนำไปสู่การตรวจจับที่ผิดพลาดน้อยลง (ผลบวกที่ผิดพลาด) แต่ในขณะที่การเพิ่มนั้นจะนำไปสู่การตรวจหาที่พลาดน้อยลง (ผลลบเท็จ)"
sensitivityDescription: "การลดความไวนั้นจะนำไปสู่การตรวจจับที่ผิดพลาดน้อยลง (ผลบวกที่ผิดพลาด)
แต่ในขณะที่การเพิ่มนั้นจะนำไปสู่การตรวจหาที่พลาดน้อยลง (ผลลบเท็จ)"
setSensitiveFlagAutomatically: "ทำเครื่องหมายว่าเป็น NSFW"
setSensitiveFlagAutomaticallyDescription: "ผลลัพธ์ของการตรวจจับภายในนั้นจะยังคงอยู่ ถึงแม้ว่าจะปิดตัวเลือกนี้"
setSensitiveFlagAutomaticallyDescription: "ผลลัพธ์ของการตรวจจับภายในนั้นจะยังคงอยู่
ถึงแม้ว่าจะปิดตัวเลือกนี้"
analyzeVideos: "เปิดใช้งานวิเคราะห์ของวิดีโอ"
analyzeVideosDescription: "การวิเคราะห์วิดีโอนอกเหนือจากรูปภาพนั้น การทำสิ่งนี้จะทำให้เพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
_emailUnavailable:
@ -915,7 +963,8 @@ _signup:
emailSent: "เราได้ส่งอีเมลยืนยันไปยังที่อยู่อีเมลของคุณแล้วนะ ({email}) โปรดคลิกลิงก์ที่รวมไว้เพื่อสร้างบัญชีให้เสร็จสิ้น"
_accountDelete:
accountDelete: "ลบบัญชีผู้ใช้"
mayTakeTime: "เนื่องจากการลบบัญชีนี้จะเป็นกระบวนการที่ต้องใช้ทรัพยากรมาก จึงอาจจะต้องใช้เวลาสักครู่ถึงจะเสร็จสมบูรณ์ ทั้งนี้ขึ้นอยู่กับจำนวนเนื้อหาที่คุณสร้างและจำนวนไฟล์ที่คุณอัปโหลดนะ"
mayTakeTime: "เนื่องจากการลบบัญชีนี้จะเป็นกระบวนการที่ต้องใช้ทรัพยากรมาก จึงอาจจะต้องใช้เวลาสักครู่ถึงจะเสร็จสมบูรณ์
ทั้งนี้ขึ้นอยู่กับจำนวนเนื้อหาที่คุณสร้างและจำนวนไฟล์ที่คุณอัปโหลดนะ"
sendEmail: "เมื่อการลบบัญชีนี้เสร็จสิ้น เราอาจจะส่งอีเมลไปยังที่อยู่อีเมลของคุณที่เคยลงทะเบียนไว้กับบัญชีนี้นะ"
requestAccountDelete: "ร้องขอให้ลบบัญชี"
started: "การลบได้เริ่มต้นขึ้น"
@ -950,11 +999,13 @@ _preferencesBackups:
inputName: "กรุณาป้อนชื่อสำหรับข้อมูลสำรองนี้"
cannotSave: "การบันทึกล้มเหลว"
nameAlreadyExists: "มีข้อมูลสำรองชื่อ \"{name}\" นี้อยู่แล้ว กรุณาป้อนชื่ออื่นนะ"
applyConfirm: "คุณต้องการใช้ข้อมูลสำรอง \"{name}\" กับอุปกรณ์นี้อย่างงั้นจริงหรอ การตั้งค่าที่มีอยู่ของอุปกรณ์นี้จะถูกเขียนทับนะ"
applyConfirm: "คุณต้องการใช้ข้อมูลสำรอง \"{name}\" กับอุปกรณ์นี้อย่างงั้นจริงหรอ
การตั้งค่าที่มีอยู่ของอุปกรณ์นี้จะถูกเขียนทับนะ"
saveConfirm: "บันทึกข้อมูลสำรองเป็น {name} มั้ย?"
deleteConfirm: "ลบข้อมูลสำรอง {name} มั้ย?"
renameConfirm: "เปลี่ยนชื่อข้อมูลสำรองนี้จาก \"{old}\" เป็น \"{new}\" หรือป่าว"
noBackups: "ไม่มีข้อมูลสำรองนะ คุณสามารถสำรองข้อมูลการตั้งค่าไคลเอนต์ของคุณบนเซิร์ฟเวอร์นี้โดยใช้ \"สร้างการสำรองข้อมูลใหม่\"ได้นะ"
noBackups: "ไม่มีข้อมูลสำรองนะ คุณสามารถสำรองข้อมูลการตั้งค่าไคลเอนต์ของคุณบนเซิร์ฟเวอร์นี้โดยใช้
\"สร้างการสำรองข้อมูลใหม่\"ได้นะ"
createdAt: "สร้างเมื่อ: {date} {time}"
updatedAt: "อัปเดตเมื่อ: {date} {time}"
cannotLoad: "การโหลดล้มเหลว"
@ -972,7 +1023,8 @@ _aboutFirefish:
source: "ซอร์สโค้ด"
translation: "รับแปลภาษา Firefish"
donate: "บริจาคให้กับ Firefish"
morePatrons: "เราขอขอบคุณสำหรับความช่วยเหลือจากผู้ช่วยอื่นๆ ที่ไม่ได้ระบุไว้ที่นี่นะ ขอขอบคุณ! 🥰"
morePatrons: "เราขอขอบคุณสำหรับความช่วยเหลือจากผู้ช่วยอื่นๆ ที่ไม่ได้ระบุไว้ที่นี่นะ
ขอขอบคุณ! 🥰"
patrons: "สมาชิกพันธมิตร"
_nsfw:
respect: "ซ่อนสื่อ NSFW"
@ -980,7 +1032,8 @@ _nsfw:
force: "ซ่อนสื่อทั้งหมด"
_mfm:
cheatSheet: "โค้ด MFM Cheat Sheet"
intro: "MFM เป็นภาษามาร์กอัปพิเศษเฉพาะของ Firefish ที่สามารถใช้ได้ในหลายที่ คุณยังสามารถดูรายการไวยากรณ์ MFM ที่มีอยู่ทั้งหมดได้ที่นี่นะ"
intro: "MFM เป็นภาษามาร์กอัปพิเศษเฉพาะของ Firefish ที่สามารถใช้ได้ในหลายที่ คุณยังสามารถดูรายการไวยากรณ์
MFM ที่มีอยู่ทั้งหมดได้ที่นี่นะ"
dummy: "Firefish ขยายโลกของ Fediverse"
mention: "กล่าวถึง"
mentionDescription: "คุณสามารถระบุผู้ใช้โดยใช้ At-Symbol และชื่อผู้ใช้ได้นะ"
@ -1069,15 +1122,18 @@ _menuDisplay:
hide: "ซ่อน"
_wordMute:
muteWords: "ปิดเสียงคำ"
muteWordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR นะ"
muteWordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข
OR นะ"
muteWordsDescription2: "ล้อมรอบคีย์เวิร์ดด้วยเครื่องหมายทับเพื่อใช้นิพจน์ทั่วไป"
softDescription: "ซ่อนโน้ตให้ตรงตามเงื่อนไขที่ตั้งไว้จากไทม์ไลน์"
hardDescription: "ป้องกันไม่ให้โน้ตย่อที่ตรงตามเงื่อนไขที่ตั้งไว้ไม่ให้ถูกเพิ่มลงในไทม์ไลน์ นอกจากนี้ โน้ตเหล่านี้จะไม่ถูกเพิ่มลงในไทม์ไลน์แม้ว่าจะมีการเปลี่ยนแปลงเงื่อนไขยังไงก็ตาม"
hardDescription: "ป้องกันไม่ให้โน้ตย่อที่ตรงตามเงื่อนไขที่ตั้งไว้ไม่ให้ถูกเพิ่มลงในไทม์ไลน์
นอกจากนี้ โน้ตเหล่านี้จะไม่ถูกเพิ่มลงในไทม์ไลน์แม้ว่าจะมีการเปลี่ยนแปลงเงื่อนไขยังไงก็ตาม"
soft: "ซอฟ"
hard: "ยาก"
mutedNotes: "ปิดเสียงโน้ต"
_instanceMute:
instanceMuteDescription: "การดำเนินการนี้จะปิดเสียง\"โน้ต/รีโน้ต\"จากอินสแตนซ์ที่อยู่ในรายการ รวมถึงบันทึกของผู้ใช้ที่ตอบกลับผู้ใช้จากอินสแตนซ์ที่ปิดเสียง"
instanceMuteDescription: "การดำเนินการนี้จะปิดเสียง\"โน้ต/รีโน้ต\"จากอินสแตนซ์ที่อยู่ในรายการ
รวมถึงบันทึกของผู้ใช้ที่ตอบกลับผู้ใช้จากอินสแตนซ์ที่ปิดเสียง"
instanceMuteDescription2: "คั่นด้วยการขึ้นบรรทัดใหม่"
title: "ซ่อนโน้ตจากอินสแตนซ์ที่มีอยู่ในรายการ"
heading: "รายชื่ออินสแตนซ์ที่ถูกปิดเสียง"
@ -1191,3 +1247,23 @@ _deck:
antenna: "เสาอากาศ"
list: "รายการ"
mentions: "พูดถึง"
noThankYou: ไม่ล่ะขอบคุณ
removeReaction: ลบรีเเอดชั่นของคุณ
renoteMute: ปิดเสียงบูส
renoteUnmute: เลิกปิดเสียงบูส
manageGroups: จัดการกลุ่ม
addInstance: เพิ่มเซิฟเวอร์
searchPlaceholder: ค้นหา Firefish
deleted: ลบแล้ว
editNote: แก้ไขโพสต์
edited: แก้ไขแล้วเมื่อ {date} {time}
jumpToPrevious: ข้ามไปที่ก่อนหน้านี้
listsDesc: ลิสต์รายการนั้นช่วยให้คุณได้สร้างไทม์ไลน์กับผู้ใช้ที่ระบุได้นะ ยังสามารถเข้าถึงได้จากหน้าไทม์ไลน์ได้อีกด้วย
enableEmojiReactions: เปิดใช้งานรีแอดชั่นอีโมจิ
selectChannel: เลือกช่อง
older: เก่ากว่านี้
newer: ใหม่กว่านี้
selectInstance: เลือกเซิฟเวอร์
showEmojisInReactionNotifications: แสดงอิโมจิในการแจ้งเตือนรีแอคชั่น
flagSpeakAsCat: พูดเหมือนแมว
cw: คำเตือนเนื้อหา

View file

@ -1,16 +1,16 @@
{
"name": "firefish",
"version": "1.0.5-dev6",
"version": "1.0.5-dev9",
"codename": "aqua",
"repository": {
"type": "git",
"url": "https://git.joinfirefish.org/firefish/firefish.git"
},
"packageManager": "pnpm@8.6.11",
"packageManager": "pnpm@8.7.1",
"private": true,
"scripts": {
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"build": "pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
"rebuild": "pnpm run clean && ./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
"build": "./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
"start": "pnpm --filter backend run start",
"start:test": "pnpm --filter backend run start:test",
"init": "pnpm run migrate",
@ -38,18 +38,19 @@
"chokidar": "^3.3.1"
},
"dependencies": {
"@bull-board/api": "5.7.2",
"@bull-board/ui": "5.7.2",
"@bull-board/api": "5.8.0",
"@bull-board/ui": "5.8.0",
"@napi-rs/cli": "^2.16.2",
"@tensorflow/tfjs": "^3.21.0",
"@tensorflow/tfjs": "^4.10.0",
"js-yaml": "4.1.0",
"seedrandom": "^3.0.5"
},
"devDependencies": {
"@biomejs/biome": "1.0.0",
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@types/node": "20.4.9",
"chalk": "4.1.2",
"@types/node": "20.5.8",
"add": "2.0.6",
"cross-env": "7.0.3",
"cypress": "10.11.0",
"execa": "5.1.1",
@ -59,8 +60,8 @@
"gulp-replace": "1.1.4",
"gulp-terser": "2.1.0",
"install-peers": "^1.0.4",
"rome": "^12.1.3",
"pnpm": "8.7.1",
"start-server-and-test": "1.15.2",
"typescript": "5.1.6"
"typescript": "5.2.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -1305,6 +1305,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
name = "migration"
version = "0.1.0"
dependencies = [
"basen",
"futures",
"indicatif",
"native-utils",

View file

@ -10,11 +10,11 @@ path = "src/lib.rs"
[features]
default = []
convert = ["dep:indicatif", "dep:futures"]
convert = ["dep:native-utils", "dep:indicatif", "dep:futures"]
[dependencies]
serde_json = "1.0.96"
native-utils = { path = "../" }
native-utils = { path = "../", optional = true }
indicatif = { version = "0.17.4", features = ["tokio"], optional = true }
tokio = { version = "1.28.2", features = ["full"] }
futures = { version = "0.3.28", optional = true }
@ -24,6 +24,7 @@ urlencoding = "2.1.2"
redis = { version = "0.23.0", features = ["tokio-rustls-comp"] }
sea-orm = "0.11.3"
url = { version = "2.4.0", features = ["serde"] }
basen = "0.1.0"
[dependencies.sea-orm-migration]
version = "0.11.0"

View file

@ -1,5 +1,7 @@
pub use sea_orm_migration::prelude::*;
use basen::BASE36;
mod m20230531_180824_drop_reversi;
mod m20230627_185451_index_note_url;
mod m20230709_000510_move_antenna_to_cache;
@ -18,3 +20,12 @@ impl MigratorTrait for Migrator {
]
}
}
pub fn get_timestamp(id: &str) -> i64 {
const TIME_2000: i64 = 946_684_800_000;
let n: Option<u64> = BASE36.decode_var_len(&id[0..8]);
match n {
None => -1,
Some(n) => n as i64 + TIME_2000,
}
}

View file

@ -1,9 +1,10 @@
use std::env;
use native_utils::util::id;
use redis::Commands;
use sea_orm_migration::prelude::*;
use crate::get_timestamp;
#[derive(DeriveMigrationName)]
pub struct Migration;
@ -31,7 +32,7 @@ impl MigrationTrait for Migration {
let all_elems: Vec<Vec<Vec<String>>> = redis_conn.xrange_all(key).unwrap(); // Get all post IDs in stream
let stream_ids = all_elems
.iter()
.map(|v| format!("{}-*", id::get_timestamp(&v[1][1]))); // Get correct stream id with timestamp
.map(|v| format!("{}-*", get_timestamp(&v[1][1]))); // Get correct stream id with timestamp
redis_conn.del::<_, ()>(key).unwrap();
for (j, v) in stream_ids.enumerate() {
redis_conn

View file

@ -42,8 +42,8 @@
"test": "pnpm run cargo:test && pnpm run build:napi && ava",
"universal": "napi universal",
"version": "napi version",
"format": "cargo fmt --all",
"lint": "cargo clippy --fix",
"format": "cargo fmt --all -- --check",
"lint": "cargo clippy --fix --allow-dirty --allow-staged && cargo fmt --all -- --check",
"cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration",
"cargo:unit": "cargo test unit_test && cargo test -F napi unit_test",
"cargo:integration": "cargo test -F noarray int_test -- --test-threads=1"

File diff suppressed because one or more lines are too long

View file

@ -16,19 +16,19 @@
"build": "pnpm swc src -d built -D",
"build:debug": "pnpm swc src -d built -s -D",
"watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check --apply *",
"lint": "pnpm biome check --apply **/*.ts ; pnpm run format",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"test": "pnpm run mocha",
"format": "pnpm rome format * --write"
"format": "pnpm biome format * --write"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
"@tensorflow/tfjs-node": "3.21.1"
},
"dependencies": {
"@bull-board/api": "5.7.2",
"@bull-board/koa": "5.7.2",
"@bull-board/ui": "5.7.2",
"@bull-board/api": "5.8.0",
"@bull-board/koa": "5.8.0",
"@bull-board/ui": "5.8.0",
"@discordapp/twemoji": "14.1.2",
"@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3",
@ -41,17 +41,16 @@
"@tensorflow/tfjs": "^4.2.0",
"adm-zip": "^0.5.10",
"ajv": "8.12.0",
"archiver": "5.3.1",
"argon2": "^0.30.3",
"archiver": "6.0.0",
"argon2": "^0.31.1",
"autolinker": "4.0.0",
"autwh": "0.1.0",
"aws-sdk": "2.1413.0",
"axios": "^1.4.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"bull": "4.11.2",
"cacheable-lookup": "7.0.0",
"cbor": "8.1.0",
"bull": "4.11.3",
"cacheable-lookup": "TheEssem/cacheable-lookup",
"chalk": "5.3.0",
"chalk-template": "0.4.0",
"chokidar": "^3.5.3",
@ -63,19 +62,19 @@
"deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1",
"feed": "4.2.2",
"file-type": "17.1.6",
"file-type": "18.5.0",
"firefish-js": "workspace:*",
"fluent-ffmpeg": "2.1.2",
"got": "12.5.3",
"got": "13.0.0",
"gunzip-maybe": "^1.4.2",
"hpagent": "0.1.2",
"hpagent": "1.2.0",
"ioredis": "5.3.2",
"ip-cidr": "3.1.0",
"is-svg": "4.3.2",
"is-svg": "5.0.0",
"js-yaml": "4.1.0",
"jsdom": "20.0.3",
"jsdom": "22.1.0",
"json5": "2.2.3",
"jsonld": "8.2.0",
"jsonld": "8.2.1",
"jsrsasign": "10.8.6",
"koa": "2.14.2",
"koa-body": "^6.0.1",
@ -89,10 +88,10 @@
"koa-slow": "2.1.0",
"koa-views": "7.0.2",
"megalodon": "workspace:*",
"meilisearch": "0.33.0",
"meilisearch": "0.34.1",
"mfm-js": "0.23.3",
"mime-types": "2.1.35",
"msgpackr": "1.9.6",
"msgpackr": "1.9.7",
"multer": "1.4.4-lts.1",
"native-utils": "link:native-utils",
"nested-property": "4.0.0",
@ -100,22 +99,22 @@
"nodemailer": "6.9.4",
"nsfwjs": "2.4.2",
"oauth": "^0.10.0",
"opencc-js": "^1.0.5",
"os-utils": "0.0.14",
"otpauth": "^9.1.4",
"parse5": "7.1.2",
"pg": "8.11.2",
"private-ip": "2.3.4",
"pg": "8.11.3",
"private-ip": "3.0.1",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"punycode": "2.3.0",
"pureimage": "0.3.15",
"pureimage": "0.4.8",
"qrcode": "1.5.3",
"qs": "6.11.2",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.20.1",
"redis-lock": "0.1.4",
"redis-semaphore": "5.4.0",
"re2": "1.20.3",
"redis-semaphore": "5.5.0",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"rndstr": "1.0.0",
@ -123,12 +122,12 @@
"sanitize-html": "2.11.0",
"seedrandom": "^3.0.5",
"semver": "7.5.4",
"sharp": "0.32.4",
"sharp": "0.32.5",
"sonic-channel": "^1.3.1",
"stringz": "2.1.0",
"summaly": "2.7.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.18.13",
"systeminformation": "5.21.3",
"tar-stream": "^3.1.6",
"tesseract.js": "^4.1.1",
"tinycolor2": "1.6.0",
@ -137,7 +136,7 @@
"typeorm": "0.3.17",
"ulid": "2.3.0",
"uuid": "9.0.0",
"web-push": "3.6.4",
"web-push": "3.6.5",
"websocket": "1.0.34",
"xev": "3.0.2"
},
@ -146,7 +145,6 @@
"@swc/core": "^1.3.75",
"@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2",
"@types/cbor": "6.0.0",
"@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.21",
"@types/js-yaml": "4.0.5",

View file

@ -30,40 +30,34 @@ const themeColor = chalk.hex("#31748f");
function greet() {
if (!envOption.quiet) {
//#region Firefish logo
const v = `v${meta.version}`;
console.log(
themeColor(
" ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄ ▄▄ ◯ ",
"██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄ ",
),
);
console.log(
themeColor(
"█ █ █ ▄ █ █ █ █ █ █ █ █ █ ○ ▄ ▄",
"██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄ ",
),
);
console.log(
themeColor(
"█ ▄▄▄█ █ █ █ █ █ ▄▄▄█ ▄▄▄█ █ ▄▄▄▄▄█ █▄█ █ ⚬ █▄▄ █▄▄ ",
"█████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄ ",
),
);
console.log(
themeColor(
"█ █▄▄▄█ █ █▄▄█▄█ █▄▄▄█ █▄▄▄█ █ █▄▄▄▄▄█ █ ▄▄▄▄▄▄ ▄",
"██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄ ",
),
);
console.log(
themeColor(
"█ ▄▄▄█ █ ▄▄ █ ▄▄▄█ ▄▄▄█ █▄▄▄▄▄ █ ▄ █ █ █ █▄▄",
"██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █ ",
),
);
console.log(
themeColor(
"█ █ █ █ █ █ █ █▄▄▄█ █ █ █▄▄▄▄▄█ █ █ █ █ █ ● ● █",
),
);
console.log(
themeColor(
"█▄▄▄█ █▄▄▄█▄▄▄█ █▄█▄▄▄▄▄▄▄█▄▄▄█ █▄▄▄█▄▄▄▄▄▄▄█▄▄█ █▄▄█ ▀▄▄▄▄▄▄▀",
"╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀ ",
),
);
//#endregion

View file

@ -70,6 +70,8 @@ if (hasConfig) {
"of",
"they",
"these",
"is",
"are",
"これ",
"それ",
"あれ",
@ -81,37 +83,30 @@ if (hasConfig) {
"あそこ",
"こちら",
"どこ",
"だれ",
"なに",
"なん",
"何",
"私",
"貴方",
"貴方方",
"僕",
"俺",
"君",
"あなた",
"我々",
"私達",
"あの人",
"あのか",
"彼女",
"彼",
"です",
"ありま",
"おりま",
"います",
"ます",
"は",
"が",
"の",
"に",
"を",
"で",
"",
"",
"から",
"まで",
"より",
"も",
"どの",
"と",
"し",
"それで",
"しかし",
])

View file

@ -1,2 +1,2 @@
// rome-ignore lint/suspicious/noExplicitAny: i have no idea
// biome-ignore lint/suspicious/noExplicitAny: i have no idea
type FIXME = any;

View file

@ -1,33 +1,40 @@
import { redisClient } from "../db/redis.js";
import { promisify } from "node:util";
import redisLock from "redis-lock";
import { Mutex } from "redis-semaphore";
/**
* Retry delay (ms) for lock acquisition
*/
const retryDelay = 100;
const lock: (key: string, timeout?: number) => Promise<() => void> = redisClient
? promisify(redisLock(redisClient, retryDelay))
: async () => () => {};
/**
* Get AP Object lock
* @param uri AP object ID
* @param timeout Lock timeout (ms), The timeout releases previous lock.
* @returns Unlock function
*/
export function getApLock(uri: string, timeout = 30 * 1000) {
return lock(`ap-object:${uri}`, timeout);
export async function getApLock(uri: string, timeout = 30 * 1000) {
const lock = new Mutex(redisClient, `ap-object:${uri}`, {
lockTimeout: timeout,
retryInterval: retryDelay,
});
await lock.acquire();
}
export function getFetchInstanceMetadataLock(
export async function getFetchInstanceMetadataLock(
host: string,
timeout = 30 * 1000,
) {
return lock(`instance:${host}`, timeout);
const lock = new Mutex(redisClient, `instance:${host}`, {
lockTimeout: timeout,
retryInterval: retryDelay,
});
await lock.acquire();
}
export function getChartInsertLock(lockKey: string, timeout = 30 * 1000) {
return lock(`chart-insert:${lockKey}`, timeout);
export async function getChartInsertLock(lockKey: string, timeout = 30 * 1000) {
const lock = new Mutex(redisClient, `chart-insert:${lockKey}`, {
lockTimeout: timeout,
retryInterval: retryDelay,
});
await lock.acquire();
}

View file

@ -32,8 +32,6 @@ export default async function (
// Interrupt if you block the announcement destination
if (await shouldBlockInstance(extractDbHost(uri))) return;
const unlock = await getApLock(uri);
try {
// Check if something with the same URI is already registered
const exist = await fetchNote(uri);
@ -60,9 +58,10 @@ export default async function (
throw e;
}
if (!(await Notes.isVisibleForMe(renote, actor.id)))
return "skip: invalid actor for this activity";
if (renote != null && !(await Notes.isVisibleForMe(renote, actor.id))) {
console.log("skip: invalid actor for this activity");
return;
}
logger.info(`Creating the (Re)Note: ${uri}`);
const activityAudience = await parseAudience(
@ -79,6 +78,6 @@ export default async function (
uri,
});
} finally {
unlock();
await getApLock(uri);
}
}

View file

@ -31,8 +31,6 @@ export default async function (
}
}
const unlock = await getApLock(uri);
try {
const exist = await fetchNote(note);
if (exist) return "skip: note exists";
@ -46,6 +44,6 @@ export default async function (
throw e;
}
} finally {
unlock();
await getApLock(uri);
}
}

View file

@ -13,8 +13,6 @@ export default async function (
): Promise<string> {
logger.info(`Deleting the Note: ${uri}`);
const unlock = await getApLock(uri);
try {
const dbResolver = new DbResolver();
const note = await dbResolver.getNoteFromApId(uri);
@ -39,6 +37,6 @@ export default async function (
await deleteNode(actor, note);
return "ok: note deleted";
} finally {
unlock();
await getApLock(uri);
}
}

View file

@ -68,13 +68,13 @@ export class LdSignature {
...options,
"@context": "https://w3id.org/identity/v1",
};
transformedOptions.type = undefined;
transformedOptions.id = undefined;
transformedOptions.signatureValue = undefined;
delete transformedOptions["type"];
delete transformedOptions["id"];
delete transformedOptions["signatureValue"];
const canonizedOptions = await this.normalize(transformedOptions);
const optionsHash = this.sha256(canonizedOptions);
const transformedData = { ...data };
transformedData.signature = undefined;
delete transformedData["signature"];
const cannonidedData = await this.normalize(transformedData);
if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`);
const documentHash = this.sha256(cannonidedData);

View file

@ -415,8 +415,6 @@ export async function resolveNote(
`host ${extractDbHost(uri)} is blocked`,
);
const unlock = await getApLock(uri);
try {
//#region Returns if already registered with this server
const exist = await fetchNote(uri);
@ -439,7 +437,7 @@ export async function resolveNote(
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
return await createNote(uri, resolver, true);
} finally {
unlock();
await getApLock(uri);
}
}

View file

@ -1,5 +1,4 @@
import { promisify } from "node:util";
import * as cbor from "cbor";
import { decode } from "msgpackr";
import define from "../../../define.js";
import {
UserProfiles,
@ -12,7 +11,6 @@ import { procedures, hash } from "../../../2fa.js";
import { publishMainStream } from "@/services/stream.js";
import { comparePassword } from "@/misc/password.js";
const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8"));
export const meta = {
@ -64,7 +62,7 @@ export default define(meta, paramDef, async (ps, user) => {
const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, "utf-8"));
const attestation = await cborDecodeFirst(ps.attestationObject);
const attestation = decode(Buffer.from(ps.attestationObject, "utf-8"));
const rpIdHash = attestation.authData.slice(0, 32);
if (!rpIdHashReal.equals(rpIdHash)) {
@ -81,7 +79,7 @@ export default define(meta, paramDef, async (ps, user) => {
const credentialIdLength = authData.readUInt16BE(53);
const credentialId = authData.slice(55, 55 + credentialIdLength);
const publicKeyData = authData.slice(55 + credentialIdLength);
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
const publicKey: Map<number, any> = decode(publicKeyData);
if (publicKey.get(3) !== -7) {
throw new Error("alg mismatch");
}

View file

@ -1,6 +1,7 @@
import { URLSearchParams } from "node:url";
import fetch from "node-fetch";
import config from "@/config/index.js";
import { Converter } from "opencc-js";
import { getAgentByUrl } from "@/misc/fetch.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { Notes } from "@/models/index.js";
@ -38,6 +39,13 @@ export const paramDef = {
required: ["noteId", "targetLang"],
} as const;
function convertChinese(convert: boolean, src: string) {
if (!convert) return src;
const converter = Converter({ from: "cn", to: "twp" });
return converter(src);
}
export default define(meta, paramDef, async (ps, user) => {
const note = await getNote(ps.noteId, user).catch((err) => {
if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
@ -93,7 +101,7 @@ export default define(meta, paramDef, async (ps, user) => {
return {
sourceLang: json.detectedLanguage?.language,
text: json.translatedText,
text: convertChinese(ps.targetLang === "zh-TW", json.translatedText),
};
}
@ -128,6 +136,6 @@ export default define(meta, paramDef, async (ps, user) => {
return {
sourceLang: json.translations[0].detected_source_language,
text: json.translations[0].text,
text: convertChinese(ps.targetLang === "zh-TW", json.translations[0].text),
};
});

View file

@ -59,7 +59,6 @@ export default class Connection {
accessToken: string,
prepareStream: string | undefined,
) {
console.log("constructor", prepareStream);
this.wsConnection = wsConnection;
this.subscriber = subscriber;
if (user) this.user = user;
@ -88,7 +87,6 @@ export default class Connection {
this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
}
console.log("prepare", prepareStream);
if (prepareStream) {
this.onWsConnectionMessage({
type: "utf8",
@ -185,8 +183,7 @@ export default class Connection {
const tl = await client.getHomeTimeline();
for (const t of tl.data) forSubscribe.push(t.id);
} catch (e: any) {
console.log(e);
console.error(e.response.data);
console.error(e);
}
} else if (simpleObj.stream === "public:local") {
this.currentSubscribe.push(["public:local"]);
@ -247,7 +244,6 @@ export default class Connection {
for (const obj of objs) {
const { type, body } = obj;
// console.log(type, body);
switch (type) {
case "readNotification":
this.onReadNotification(body);

View file

@ -3,10 +3,11 @@
"name": "Firefish",
"description": "An open source, decentralized social media platform that's free forever!",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#1f1d2e",
"theme_color": "#31748f",
"orientation": "portrait-primary",
"orientation": "natural",
"icons": [
{
"src": "/static-assets/icons/192.png",
@ -21,7 +22,7 @@
"purpose": "any"
},
{
"src": "/static-assets/icons/maskable.png",
"src": "/static-assets/icons/512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"

View file

@ -11,7 +11,6 @@ export const manifestHandler = async (ctx: Koa.Context) => {
const instance = await fetchMeta(true);
res.short_name = instance.name || "Firefish";
res.name = instance.name || "Firefish";
if (instance.themeColor) res.theme_color = instance.themeColor;
for (const icon of res.icons) {
icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`;

View file

@ -7,16 +7,16 @@ doctype html
//
-
▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄ ▄▄ ◯
█ █ █ ▄ █ █ █ █ █ █ █ █ █ ○ ▄ ▄
█ ▄▄▄█ █ █ █ █ █ ▄▄▄█ ▄▄▄█ █ ▄▄▄▄▄█ █▄█ █ ⚬ █▄▄ █▄▄
█ █▄▄▄█ █ █▄▄█▄█ █▄▄▄█ █▄▄▄█ █ █▄▄▄▄▄█ █ ▄▄▄▄▄▄ ▄
█ ▄▄▄█ █ ▄▄ █ ▄▄▄█ ▄▄▄█ █▄▄▄▄▄ █ ▄ █ █ █ █▄▄
█ █ █ █ █ █ █ █▄▄▄█ █ █ █▄▄▄▄▄█ █ █ █ █ █ ● ● █
█▄▄▄█ █▄▄▄█▄▄▄█ █▄█▄▄▄▄▄▄▄█▄▄▄█ █▄▄▄█▄▄▄▄▄▄▄█▄▄█ █▄▄█ ▀▄▄▄▄▄▄▀
██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄
██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄
█████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄
██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄
██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █
╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀
Thank you for using Firefish!
If you are reading this message... how about joining the development?
If you're reading this message... how about helping out with development?
https://git.joinfirefish.org/firefish/firefish
html

View file

@ -430,7 +430,6 @@ export default abstract class Chart<T extends Schema> {
? `${this.name}:${date}:${span}:${group}`
: `${this.name}:${date}:${span}`;
const unlock = await getChartInsertLock(lockKey);
try {
// ロック内でもう1回チェックする
const currentLog = (await repository.findOneBy({
@ -466,14 +465,14 @@ export default abstract class Chart<T extends Schema> {
return log;
} finally {
unlock();
await getChartInsertLock(lockKey);
}
}
protected commit(diff: Commit<T>, group: string | null = null): void {
for (const [k, v] of Object.entries(diff)) {
if (v == null || v === 0 || (Array.isArray(v) && v.length === 0))
// rome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
// biome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
delete diff[k];
}
this.buffer.push({

View file

@ -15,8 +15,6 @@ export async function fetchInstanceMetadata(
instance: Instance,
force = false,
): Promise<void> {
const unlock = await getFetchInstanceMetadataLock(instance.host);
if (!force) {
const _instance = await Instances.findOneBy({ host: instance.host });
const now = Date.now();
@ -24,7 +22,7 @@ export async function fetchInstanceMetadata(
_instance?.infoUpdatedAt &&
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
) {
unlock();
await getFetchInstanceMetadataLock(instance.host);
return;
}
}
@ -53,7 +51,7 @@ export async function fetchInstanceMetadata(
} as Record<string, any>;
if (info) {
updates.softwareName = info.software?.name.toLowerCase();
updates.softwareName = info.software?.name?.toLowerCase() || null;
updates.softwareVersion = info.software?.version;
updates.openRegistrations = info.openRegistrations;
updates.maintainerName = info.metadata
@ -80,24 +78,24 @@ export async function fetchInstanceMetadata(
} catch (e) {
logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
} finally {
unlock();
await getFetchInstanceMetadataLock(instance.host);
}
}
type NodeInfo = {
openRegistrations?: any;
openRegistrations?: boolean;
software?: {
name?: any;
version?: any;
name?: string;
version?: string;
};
metadata?: {
name?: any;
nodeName?: any;
nodeDescription?: any;
description?: any;
name?: string;
nodeName?: string;
nodeDescription?: string;
description?: string;
maintainer?: {
name?: any;
email?: any;
name?: string;
email?: string;
};
};
};

View file

@ -169,7 +169,7 @@ export default async (
data: Option,
silent = false,
) =>
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => {
const dontFederateInitially = data.visibility === "hidden";

View file

@ -1,6 +1,7 @@
{
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
"plugins": ["file-progress", "prettier"],
"ignorePatterns": ["**/*.json5"],
"rules": {
"file-progress/activate": 1
}

View file

@ -5,9 +5,9 @@
"watch": "pnpm vite build --watch --mode development",
"build": "pnpm vite build",
"build:debug": "pnpm run build",
"lint": "pnpm rome check **/*.ts --apply && pnpm run lint:vue",
"lint:vue": "pnpm paralint --ext .vue --fix '**/*.vue' --cache",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
"lint": "pnpm biome check **/*.ts --apply ; pnpm run lint:vue",
"lint:vue": "pnpm eslint src --fix '**/*.vue' --cache ; pnpm run format",
"format": "pnpm biome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
},
"devDependencies": {
"@discordapp/twemoji": "14.1.2",
@ -16,7 +16,7 @@
"@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "5.0.0",
"@rollup/plugin-json": "6.0.0",
"@rollup/pluginutils": "^5.0.3",
"@rollup/pluginutils": "^5.0.4",
"@syuilo/aiscript": "0.11.1",
"@types/escape-regexp": "0.0.1",
"@types/glob": "8.1.0",
@ -28,15 +28,15 @@
"@types/seedrandom": "3.0.5",
"@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3",
"@types/uuid": "9.0.2",
"@vitejs/plugin-vue": "4.3.1",
"@types/uuid": "9.0.3",
"@vitejs/plugin-vue": "4.3.4",
"@vue/compiler-sfc": "3.3.4",
"autobind-decorator": "2.4.0",
"autosize": "6.0.1",
"blurhash": "2.0.5",
"broadcast-channel": "5.2.0",
"broadcast-channel": "5.3.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"chart.js": "4.3.3",
"chart.js": "4.4.0",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "^2.0.1",
"chartjs-plugin-gradient": "0.6.1",
@ -63,36 +63,35 @@
"katex": "0.16.8",
"matter-js": "0.19.0",
"mfm-js": "0.23.3",
"paralint": "^1.2.1",
"photoswipe": "5.3.8",
"prettier": "3.0.2",
"prettier": "3.0.3",
"prettier-plugin-vue": "1.1.6",
"prismjs": "1.29.0",
"punycode": "2.3.0",
"rndstr": "1.0.0",
"rollup": "3.28.0",
"rollup": "3.28.1",
"s-age": "1.1.2",
"sass": "1.66.0",
"sass": "1.66.1",
"seedrandom": "3.0.5",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"swiper": "10.2.0",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.155.0",
"three": "0.156.0",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tsc-alias": "1.8.7",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typescript": "5.1.6",
"typescript": "5.2.2",
"unicode-emoji-json": "^0.4.0",
"uuid": "9.0.0",
"vanilla-tilt": "1.8.1",
"vite": "4.4.9",
"vite-plugin-compression": "^0.5.1",
"vue": "3.3.4",
"vue-draggable-plus": "^0.2.5",
"vue-draggable-plus": "^0.2.6",
"vue-isyourpasswordsafe": "^2.0.0",
"vue-plyr": "^7.0.0",
"vue-prism-editor": "2.0.0-alpha.2"

View file

@ -1,10 +1,10 @@
import { defineAsyncComponent, reactive } from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import { i18n } from "./i18n";
import { del, get, set } from "@/scripts/idb-proxy";
import { apiUrl } from "@/config";
import { waiting, api, popup, popupMenu, success, alert } from "@/os";
import { unisonReload, reloadChannel } from "@/scripts/unison-reload";
import { alert, api, popup, popupMenu, success, waiting } from "@/os";
import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
// TODO: 他のタブと永続化されたstateを同期
@ -28,7 +28,7 @@ export async function signout() {
const accounts = await getAccounts();
//#region Remove service worker registration
// #region Remove service worker registration
try {
if (navigator.serviceWorker.controller) {
const registration = await navigator.serviceWorker.ready;
@ -52,7 +52,7 @@ export async function signout() {
});
}
} catch (err) {}
//#endregion
// #endregion
document.cookie = "igi=; path=/";

View file

@ -8,9 +8,11 @@
<div :class="$style.time">
<MkTime :time="announcement.createdAt" />
<div v-if="announcement.updatedAt">
{{ i18n.ts.updatedAt }}:
<MkTime :time="announcement.createdAt" />
</div>
<small>
{{ i18n.ts.updatedAt }}:
<MkTime :time="announcement.createdAt" />
</small>
</span>
</div>
<Mfm :text="text" />
<img
@ -80,6 +82,6 @@ const gotIt = () => {
}
.gotIt {
margin: 8px 0 0 0;
margin: 1rem 0 1rem 2rem;
}
</style>

View file

@ -199,7 +199,7 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from "vue";
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import * as Acct from "firefish-js/built/acct";
import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue";
@ -281,7 +281,9 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null);
let disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(null);
const disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(
null,
);
const okButtonDisabled = computed<boolean>(() => {
if (props.input) {
if (props.input.minLength) {

View file

@ -39,7 +39,7 @@
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from "vue";
import * as Misskey from "firefish-js";
import type * as Misskey from "firefish-js";
import copyToClipboard from "@/scripts/copy-to-clipboard";
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
import bytes from "@/filters/bytes";
@ -160,7 +160,7 @@ function rename() {
if (canceled) return;
os.api("drive/files/update", {
fileId: props.file.id,
name: name,
name,
});
});
}
@ -179,7 +179,7 @@ function describe() {
{
done: (result) => {
if (!result || result.canceled) return;
let comment = result.result;
const comment = result.result;
os.api("drive/files/update", {
fileId: props.file.id,
comment: comment.length === 0 ? null : comment,

View file

@ -38,7 +38,7 @@
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from "vue";
import * as Misskey from "firefish-js";
import type * as Misskey from "firefish-js";
import * as os from "@/os";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
@ -131,7 +131,7 @@ function onDrop(ev: DragEvent) {
return;
}
//#region
// #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile);
@ -141,9 +141,9 @@ function onDrop(ev: DragEvent) {
folderId: props.folder.id,
});
}
//#endregion
// #endregion
//#region
// #region
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
if (driveFolder != null && driveFolder !== "") {
const folder = JSON.parse(driveFolder);
@ -175,7 +175,7 @@ function onDrop(ev: DragEvent) {
}
});
}
//#endregion
// #endregion
}
function onDragstart(ev: DragEvent) {
@ -207,7 +207,7 @@ function rename() {
if (canceled) return;
os.api("drive/folders/update", {
folderId: props.folder.id,
name: name,
name,
});
});
}

View file

@ -15,7 +15,7 @@
<script lang="ts" setup>
import { ref } from "vue";
import * as Misskey from "firefish-js";
import type * as Misskey from "firefish-js";
import * as os from "@/os";
import { i18n } from "@/i18n";
@ -86,7 +86,7 @@ function onDrop(ev: DragEvent) {
return;
}
//#region
// #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile);
@ -96,9 +96,9 @@ function onDrop(ev: DragEvent) {
folderId: props.folder ? props.folder.id : null,
});
}
//#endregion
// #endregion
//#region
// #region
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
if (driveFolder != null && driveFolder !== "") {
const folder = JSON.parse(driveFolder);
@ -110,7 +110,7 @@ function onDrop(ev: DragEvent) {
parentId: props.folder ? props.folder.id : null,
});
}
//#endregion
// #endregion
}
</script>

View file

@ -139,7 +139,7 @@ import {
ref,
watch,
} from "vue";
import * as Misskey from "firefish-js";
import type * as Misskey from "firefish-js";
import MkButton from "./MkButton.vue";
import XNavFolder from "@/components/MkDrive.navFolder.vue";
import XFolder from "@/components/MkDrive.folder.vue";
@ -294,7 +294,7 @@ function onDrop(ev: DragEvent): any {
return;
}
//#region
// #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile);
@ -305,9 +305,9 @@ function onDrop(ev: DragEvent): any {
folderId: folder.value ? folder.value.id : null,
});
}
//#endregion
// #endregion
//#region
// #region
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
if (driveFolder != null && driveFolder !== "") {
const droppedFolder = JSON.parse(driveFolder);
@ -339,7 +339,7 @@ function onDrop(ev: DragEvent): any {
}
});
}
//#endregion
// #endregion
}
function selectLocalFile() {
@ -354,7 +354,7 @@ function urlUpload() {
}).then(({ canceled, result: url }) => {
if (canceled || !url) return;
os.api("drive/files/upload-from-url", {
url: url,
url,
folderId: folder.value ? folder.value.id : undefined,
});
@ -372,7 +372,7 @@ function createFolder() {
}).then(({ canceled, result: name }) => {
if (canceled) return;
os.api("drive/folders/create", {
name: name,
name,
parentId: folder.value ? folder.value.id : undefined,
}).then((createdFolder) => {
addFolder(createdFolder, true);
@ -389,7 +389,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
if (canceled) return;
os.api("drive/folders/update", {
folderId: folderToRename.id,
name: name,
name,
}).then((updatedFolder) => {
// FIXME:
move(updatedFolder);

View file

@ -68,7 +68,7 @@ const is = computed(() => {
"application/x-tar",
"application/gzip",
"application/x-7z-compressed",
].some((archiveType) => archiveType === props.file.type)
].includes(props.file.type)
)
return "archive";
return "unknown";

View file

@ -37,7 +37,7 @@
<script lang="ts" setup>
import { ref } from "vue";
import * as Misskey from "firefish-js";
import type * as Misskey from "firefish-js";
import XDrive from "@/components/MkDrive.vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import number from "@/filters/number";

View file

@ -15,7 +15,7 @@
<script lang="ts" setup>
import {} from "vue";
import * as Misskey from "firefish-js";
import type * as Misskey from "firefish-js";
import XDrive from "@/components/MkDrive.vue";
import XWindow from "@/components/MkWindow.vue";
import { i18n } from "@/i18n";

View file

@ -48,7 +48,7 @@
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import { onMounted, ref, watch } from "vue";
import { addSkinTone } from "@/scripts/emojilist";
const props = defineProps<{

View file

@ -1,5 +1,5 @@
<template>
<FocusTrap v-bind:active="isActive">
<FocusTrap :active="isActive">
<div
class="omfetrab"
:class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]"
@ -163,14 +163,15 @@
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from "vue";
import * as Misskey from "firefish-js";
import { computed, onMounted, ref, watch } from "vue";
import type * as Misskey from "firefish-js";
import { FocusTrap } from "focus-trap-vue";
import XSection from "@/components/MkEmojiPicker.section.vue";
import type { UnicodeEmojiDef } from "@/scripts/emojilist";
import {
emojilist,
unicodeEmojiCategories,
UnicodeEmojiDef,
getNicelyLabeledCategory,
unicodeEmojiCategories,
} from "@/scripts/emojilist";
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
import Ripple from "@/components/MkRipple.vue";
@ -180,7 +181,6 @@ import { deviceKind } from "@/scripts/device-kind";
import { emojiCategories, instance } from "@/instance";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
import { FocusTrap } from "focus-trap-vue";
const props = withDefaults(
defineProps<{

View file

@ -8,7 +8,7 @@
<script lang="ts" setup>
import { ref } from "vue";
import * as Misskey from "firefish-js";
import type * as Misskey from "firefish-js";
import * as os from "@/os";
const meta = ref<Misskey.entities.DetailedInstanceMetadata>();

View file

@ -1,14 +1,15 @@
<template>
<button
v-if="!hideMenu"
v-tooltip="i18n.ts.menu"
class="menu _button"
@click.stop="menu"
v-tooltip="i18n.ts.menu"
>
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
</button>
<button
v-if="$i != null && $i.id != user.id"
v-tooltip="full ? null : `${state} ${user.name || user.username}`"
class="kpoogebi _button follow-button"
:class="{
wait,
@ -18,9 +19,8 @@
blocking: isBlocking,
}"
:disabled="wait"
@click.stop="onClick"
:aria-label="`${state} ${user.name || user.username}`"
v-tooltip="full ? null : `${state} ${user.name || user.username}`"
@click.stop="onClick"
>
<template v-if="!wait">
<template v-if="isBlocking">
@ -88,13 +88,13 @@ const props = withDefaults(
const isBlocking = computed(() => props.user.isBlocking);
let state = ref(i18n.ts.processing);
const state = ref(i18n.ts.processing);
let isFollowing = ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = ref(
const isFollowing = ref(props.user.isFollowing);
const hasPendingFollowRequestFromYou = ref(
props.user.hasPendingFollowRequestFromYou,
);
let wait = ref(false);
const wait = ref(false);
const connection = stream.useChannel("main");
if (props.user.isFollowing == null) {

View file

@ -64,7 +64,6 @@
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue";
@ -77,11 +76,11 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let dialog: InstanceType<typeof XModalWindow> = ref();
const dialog: InstanceType<typeof XModalWindow> = ref();
let username = ref("");
let email = ref("");
let processing = ref(false);
const username = ref("");
const email = ref("");
const processing = ref(false);
async function onSubmit() {
processing.value = true;

View file

@ -3,7 +3,7 @@
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
import { defineAsyncComponent, defineComponent } from "vue";
export default defineComponent({
components: {

View file

@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { onMounted, nextTick, watch, shallowRef, ref } from "vue";
import { nextTick, onMounted, ref, shallowRef, watch } from "vue";
import { Chart } from "chart.js";
import * as os from "@/os";
import { defaultStore } from "@/store";
@ -26,8 +26,8 @@ const props = defineProps<{
const rootEl = shallowRef<HTMLDivElement>(null);
const chartEl = shallowRef<HTMLCanvasElement>(null);
const now = new Date();
let chartInstance: Chart = null;
let fetching = ref(true);
let chartInstance: Chart = null,
fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip({
position: "middle",
@ -233,7 +233,7 @@ async function renderChart() {
return ["Active: " + v.v];
},
},
//mode: 'index',
// mode: 'index',
animation: {
duration: 0,
},

View file

@ -28,7 +28,6 @@
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import type * as misskey from "firefish-js";
import bytes from "@/filters/bytes";
import number from "@/filters/number";

View file

@ -49,7 +49,7 @@ const props = withDefaults(
);
const canvas = ref<HTMLCanvasElement>();
let loaded = ref(false);
const loaded = ref(false);
function draw() {
if (props.hash == null || canvas.value == null) return;

View file

@ -11,8 +11,8 @@
v-if="closeable"
v-tooltip="i18n.ts.close"
class="_buttonIcon close"
@click.stop="close"
:aria-label="i18n.t('close')"
@click.stop="close"
>
<i class="ph-x ph-bold ph-lg"></i>
</button>

View file

@ -26,7 +26,7 @@
<script lang="ts" setup>
import { ref } from "vue";
import * as firefish from "firefish-js";
import type * as firefish from "firefish-js";
import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
@ -35,7 +35,7 @@ const props = defineProps<{
instance: firefish.entities.Instance;
}>();
let chartValues = ref<number[] | null>(null);
const chartValues = ref<number[] | null>(null);
os.apiGet("charts/instance", {
host: props.instance.host,

View file

@ -58,11 +58,11 @@
<script lang="ts" setup>
import { ref } from "vue";
import type { Instance } from "firefish-js/built/entities";
import MkInput from "@/components/form/input.vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import * as os from "@/os";
import { i18n } from "@/i18n";
import { Instance } from "firefish-js/built/entities";
const emit = defineEmits<{
(ev: "ok", selected: Instance): void;
@ -70,10 +70,10 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let hostname = ref("");
let instances: Instance[] = ref([]);
let selected: Instance | null = ref(null);
let dialogEl = ref<InstanceType<typeof XModalWindow>>();
const hostname = ref("");
const instances: Instance[] = ref([]);
const selected: Instance | null = ref(null);
const dialogEl = ref<InstanceType<typeof XModalWindow>>();
let searchOrderLatch = 0;
const search = () => {

View file

@ -116,11 +116,11 @@ import { initChart } from "@/scripts/init-chart";
initChart();
const chartLimit = 500;
let chartSpan = ref<"hour" | "day">("hour");
let chartSrc = ref("active-users");
let heatmapSrc = ref("active-users");
let subDoughnutEl = shallowRef<HTMLCanvasElement>();
let pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const chartSpan = ref<"hour" | "day">("hour");
const chartSrc = ref("active-users");
const heatmapSrc = ref("active-users");
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const { handler: externalTooltipHandler1 } = useChartTooltip({
position: "middle",

View file

@ -1,8 +1,8 @@
<template>
<div
class="hpaizdrt"
v-tooltip="capitalize(instance.softwareName)"
ref="ticker"
v-tooltip="capitalize(instance.softwareName)"
class="hpaizdrt"
:style="bg"
>
<img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" />
@ -26,7 +26,7 @@ const props = defineProps<{
};
}>();
let ticker = ref<HTMLElement | null>(null);
const ticker = ref<HTMLElement | null>(null);
// if no instance data is given, this is for the local instance
const instance = props.instance ?? {

View file

@ -1,5 +1,5 @@
<template>
<div class="media" v-size="{ max: [350] }">
<div v-size="{ max: [350] }" class="media">
<button v-if="hide" class="hidden" @click="hide = false">
<ImgWithBlurhash
:hash="media.blurhash"
@ -89,7 +89,7 @@
</template>
<script lang="ts" setup>
import { watch, ref, computed } from "vue";
import { computed, ref, watch } from "vue";
import VuePlyr from "vue-plyr";
import "vue-plyr/dist/vue-plyr.css";
import type * as misskey from "firefish-js";
@ -104,7 +104,7 @@ const props = defineProps<{
raw?: boolean;
}>();
let hide = ref(true);
const hide = ref(true);
const plyr = ref();

View file

@ -71,7 +71,7 @@ const props = withDefaults(
);
const audioEl = ref<HTMLAudioElement | null>();
let hide = ref(true);
const hide = ref(true);
function volumechange() {
if (audioEl.value)

View file

@ -29,7 +29,7 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import PhotoSwipeLightbox from "photoswipe/lightbox";
import PhotoSwipe from "photoswipe";
import "photoswipe/style.css";
@ -125,11 +125,11 @@ onMounted(() => {
className: "pwsp__alt-text-container",
appendTo: "wrapper",
onInit: (el, pwsp) => {
let textBox = document.createElement("p");
const textBox = document.createElement("p");
textBox.className = "pwsp__alt-text";
el.appendChild(textBox);
let preventProp = function (ev: Event): void {
const preventProp = function (ev: Event): void {
ev.stopPropagation();
};

View file

@ -14,7 +14,7 @@
<script lang="ts" setup>
import { nextTick, onMounted, ref } from "vue";
import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu";
import type { MenuItem } from "@/types/menu";
const props = defineProps<{
items: MenuItem[];

View file

@ -14,8 +14,8 @@
width: width && !asDrawer ? width + 'px' : '',
maxHeight: maxHeight ? maxHeight + 'px' : '',
}"
@contextmenu.self="(e) => e.preventDefault()"
tabindex="-1"
@contextmenu.self="(e) => e.preventDefault()"
>
<template v-for="item in items2">
<div v-if="item === null" class="divider"></div>
@ -47,7 +47,7 @@
v-if="item.avatar"
:user="item.avatar"
class="avatar"
disableLink
disable-link
/>
<span :style="item.textStyle || ''">{{
item.text
@ -100,7 +100,7 @@
<MkAvatar
:user="item.user"
class="avatar"
disableLink
disable-link
/><MkUserName :user="item.user" />
<span
v-if="item.indicate"
@ -168,7 +168,7 @@
v-if="item.avatar"
:user="item.avatar"
class="avatar"
disableLink
disable-link
/>
<span :style="item.textStyle || ''">{{
item.text
@ -210,11 +210,16 @@ import {
ref,
watch,
} from "vue";
import { FocusTrap } from "focus-trap-vue";
import FormSwitch from "@/components/form/switch.vue";
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
import type {
InnerMenuItem,
MenuAction,
MenuItem,
MenuPending,
} from "@/types/menu";
import * as os from "@/os";
import { i18n } from "@/i18n";
import { FocusTrap } from "focus-trap-vue";
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
const focusTrap = ref();
@ -233,13 +238,13 @@ const emit = defineEmits<{
(ev: "close", actioned?: boolean): void;
}>();
let itemsEl = ref<HTMLDivElement>();
const itemsEl = ref<HTMLDivElement>();
let items2: InnerMenuItem[] = ref([]);
const items2: InnerMenuItem[] = ref([]);
let child = ref<InstanceType<typeof XChild>>();
const child = ref<InstanceType<typeof XChild>>();
let childShowingItem = ref<MenuItem | null>();
const childShowingItem = ref<MenuItem | null>();
watch(
() => props.items,
@ -267,8 +272,8 @@ watch(
},
);
let childMenu = ref<MenuItem[] | null>();
let childTarget = ref<HTMLElement | null>();
const childMenu = ref<MenuItem[] | null>();
const childTarget = ref<HTMLElement | null>();
function closeChild() {
childMenu.value = null;

View file

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { watch, ref } from "vue";
import { ref, watch } from "vue";
import { v4 as uuid } from "uuid";
import tinycolor from "tinycolor2";
import { useInterval } from "@/scripts/use-interval";
@ -37,10 +37,10 @@ const props = defineProps<{
const viewBoxX = 50;
const viewBoxY = 50;
const gradientId = uuid();
let polylinePoints = ref("");
let polygonPoints = ref("");
let headX = ref<number | null>(null);
let headY = ref<number | null>(null);
const polylinePoints = ref("");
const polygonPoints = ref("");
const headX = ref<number | null>(null);
const headY = ref<number | null>(null);
const accent = tinycolor(
getComputedStyle(document.documentElement).getPropertyValue("--accent"),
);

View file

@ -25,6 +25,7 @@
<div
v-show="manualShowing != null ? manualShowing : showing"
v-hotkey.global="keymap"
v-focus
:class="[
$style.root,
{
@ -44,7 +45,6 @@
'--transformOrigin': transformOrigin,
}"
tabindex="-1"
v-focus
>
<div
class="_modalBg data-cy-bg"
@ -78,20 +78,20 @@
<script lang="ts" setup>
import {
computed,
nextTick,
onMounted,
watch,
provide,
onUnmounted,
provide,
ref,
shallowRef,
computed,
watch,
} from "vue";
import { FocusTrap } from "focus-trap-vue";
import * as os from "@/os";
import { isTouchUsing } from "@/scripts/touch";
import { defaultStore } from "@/store";
import { deviceKind } from "@/scripts/device-kind";
import { FocusTrap } from "focus-trap-vue";
function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === "BODY") return null;
@ -139,13 +139,13 @@ const emit = defineEmits<{
provide("modal", true);
let maxHeight = ref<number>();
let fixed = ref(false);
let transformOrigin = ref("center");
let showing = ref(true);
let content = shallowRef<HTMLElement>();
const maxHeight = ref<number>();
const fixed = ref(false);
const transformOrigin = ref("center");
const showing = ref(true);
const content = shallowRef<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority);
let useSendAnime = ref(false);
const useSendAnime = ref(false);
const type = computed<ModalTypes>(() => {
if (props.preferType === "auto") {
if (
@ -164,7 +164,7 @@ const type = computed<ModalTypes>(() => {
const isEnableBgTransparent = computed(
() => props.transparentBg && type.value === "popup",
);
let transitionName = computed(() =>
const transitionName = computed(() =>
defaultStore.state.animation
? useSendAnime.value
? "send"
@ -175,7 +175,7 @@ let transitionName = computed(() =>
: "modal"
: "",
);
let transitionDuration = computed(() =>
const transitionDuration = computed(() =>
transitionName.value === "send"
? 400
: transitionName.value === "modal-popup"
@ -235,8 +235,7 @@ const align = () => {
const width = content.value!.offsetWidth;
const height = content.value!.offsetHeight;
let left;
let top;
let left, top;
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
@ -321,8 +320,8 @@ const align = () => {
left = 0;
}
let transformOriginX = "center";
let transformOriginY = "center";
let transformOriginX = "center",
transformOriginY = "center";
if (
top >=

View file

@ -28,8 +28,8 @@
</span>
<button
class="_button"
@click="$refs.modal.close()"
:aria-label="i18n.t('close')"
@click="$refs.modal.close()"
>
<i class="ph-x ph-bold ph-lg"></i>
</button>
@ -52,7 +52,8 @@
</template>
<script lang="ts" setup>
import { ComputedRef, provide, ref, computed } from "vue";
import type { ComputedRef } from "vue";
import { computed, provide, ref } from "vue";
import MkModal from "@/components/MkModal.vue";
import { popout as _popout } from "@/scripts/popout";
import copyToClipboard from "@/scripts/copy-to-clipboard";
@ -60,7 +61,8 @@ import { url } from "@/config";
import * as os from "@/os";
import { mainRouter, routes } from "@/router";
import { i18n } from "@/i18n";
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
import type { PageMetadata } from "@/scripts/page-metadata";
import { provideMetadataReceiver } from "@/scripts/page-metadata";
import { Router } from "@/nirax";
const props = defineProps<{
@ -76,12 +78,12 @@ const router = new Router(routes, props.initialPath);
router.addListener("push", (ctx) => {});
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let rootEl = ref();
let modal = ref<InstanceType<typeof MkModal>>();
let path = ref(props.initialPath);
let width = ref(860);
let height = ref(660);
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
const rootEl = ref();
const modal = ref<InstanceType<typeof MkModal>>();
const path = ref(props.initialPath);
const width = ref(860);
const height = ref(660);
const history = [];
provide("router", router);

View file

@ -25,10 +25,10 @@
<div ref="headerEl" class="header">
<button
v-if="props.withOkButton"
v-tooltip="i18n.ts.close"
:aria-label="i18n.t('close')"
class="_button"
@click="$emit('close')"
v-tooltip="i18n.ts.close"
>
<i class="ph-x ph-bold ph-lg"></i>
</button>
@ -92,9 +92,9 @@ const emit = defineEmits<{
(event: "ok"): void;
}>();
let modal = shallowRef<InstanceType<typeof MkModal>>();
let rootEl = shallowRef<HTMLElement>();
let headerEl = shallowRef<HTMLElement>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const rootEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();
const close = (ev) => {
modal.value?.close(ev);

View file

@ -1,15 +1,15 @@
<template>
<div
:aria-label="accessibleLabel"
v-if="!muted.muted"
v-show="!isDeleted"
:id="appearNote.id"
ref="el"
v-hotkey="keymap"
v-size="{ max: [500, 350] }"
:aria-label="accessibleLabel"
class="tkcbzcuz note-container"
:tabindex="!isDeleted ? '-1' : null"
:class="{ renote: isRenote }"
:id="appearNote.id"
>
<MkNoteSub
v-if="appearNote.reply && !detailedView && !collapsedReply"
@ -19,10 +19,10 @@
<div
v-if="!detailedView"
class="note-context"
@click="noteClick"
:class="{
collapsedReply: collapsedReply && appearNote.reply,
}"
@click="noteClick"
>
<div class="line"></div>
<div v-if="appearNote._prId_" class="info">
@ -87,11 +87,11 @@
</div>
<article
class="article"
@contextmenu.stop="onContextmenu"
@click="noteClick"
:style="{
cursor: expandOnNoteClick && !detailedView ? 'pointer' : '',
}"
@contextmenu.stop="onContextmenu"
@click="noteClick"
>
<div class="main">
<div class="header-container">
@ -103,8 +103,8 @@
class="text"
:note="appearNote"
:detailed="true"
:detailedView="detailedView"
:parentId="appearNote.parentId"
:detailed-view="detailedView"
:parent-id="appearNote.parentId"
@push="(e) => router.push(notePage(e))"
@focusfooter="footerEl.focus()"
@expanded="(e) => setPostExpanded(e)"
@ -171,7 +171,7 @@
class="button"
:note="appearNote"
:count="appearNote.renoteCount"
:detailedView="detailedView"
:detailed-view="detailedView"
/>
<XStarButtonNoEmoji
v-if="!enableEmojiReactions"
@ -212,9 +212,9 @@
appearNote.myReaction != null
"
ref="reactButton"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
class="button _button reacted"
@click.stop="undoReact(appearNote)"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
>
<i class="ph-minus ph-bold ph-lg"></i>
</button>
@ -259,8 +259,8 @@ import { computed, inject, onMounted, ref } from "vue";
import * as mfm from "mfm-js";
import type { Ref } from "vue";
import type * as misskey from "firefish-js";
import MkNoteSub from "@/components/MkNoteSub.vue";
import MkSubNoteContent from "./MkSubNoteContent.vue";
import MkNoteSub from "@/components/MkNoteSub.vue";
import XNoteHeader from "@/components/MkNoteHeader.vue";
import XRenoteButton from "@/components/MkRenoteButton.vue";
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
@ -271,7 +271,7 @@ import MkVisibility from "@/components/MkVisibility.vue";
import copyToClipboard from "@/scripts/copy-to-clipboard";
import { url } from "@/config";
import { pleaseLogin } from "@/scripts/please-login";
import { focusPrev, focusNext } from "@/scripts/focus";
import { focusNext, focusPrev } from "@/scripts/focus";
import { getWordSoftMute } from "@/scripts/check-word-mute";
import { useRouter } from "@/router";
import { userPage } from "@/filters/user";
@ -297,7 +297,7 @@ const props = defineProps<{
const inChannel = inject("inChannel", null);
let note = ref(deepClone(props.note));
const note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -333,7 +333,7 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>();
let appearNote = computed(() =>
const appearNote = computed(() =>
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
);
const isMyRenote = $i && $i.id === note.value.userId;
@ -385,7 +385,7 @@ function react(viaKeyboard = false): void {
(reaction) => {
os.api("notes/reactions/create", {
noteId: appearNote.value.id,
reaction: reaction,
reaction,
});
},
() => {
@ -516,7 +516,7 @@ function showRenoteMenu(viaKeyboard = false): void {
],
renoteTime.value,
{
viaKeyboard: viaKeyboard,
viaKeyboard,
},
);
}
@ -560,7 +560,7 @@ function readPromo() {
isDeleted.value = true;
}
let postIsExpanded = ref(false);
const postIsExpanded = ref(false);
function setPostExpanded(val: boolean) {
postIsExpanded.value = val;

View file

@ -10,27 +10,27 @@
:class="{ renote: isRenote }"
>
<MkNoteSub
v-if="conversation"
v-for="note in conversation"
v-if="conversation"
:key="note.id"
class="reply-to"
:note="note"
:detailedView="true"
:detailed-view="true"
/>
<MkLoading v-else-if="note.reply" mini />
<MkNoteSub
v-if="note.reply"
:note="note.reply"
class="reply-to"
:detailedView="true"
:detailed-view="true"
/>
<MkNote
ref="noteEl"
@contextmenu.stop="onContextmenu"
tabindex="-1"
:note="note"
detailedView
detailed-view
@contextmenu.stop="onContextmenu"
></MkNote>
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
@ -41,22 +41,22 @@
}}</span>
{{ i18n.ts._notification._types.reply }}
</option>
<option value="renotes" v-if="note.renoteCount > 0">
<option v-if="note.renoteCount > 0" value="renotes">
<!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
<span class="count">{{ note.renoteCount }}</span>
{{ i18n.ts._notification._types.renote }}
</option>
<option value="reactions" v-if="reactionsCount > 0">
<option v-if="reactionsCount > 0" value="reactions">
<!-- <i class="ph-smiley ph-bold ph-lg"></i> -->
<span class="count">{{ reactionsCount }}</span>
{{ i18n.ts.reaction }}
</option>
<option value="quotes" v-if="directQuotes?.length > 0">
<option v-if="directQuotes?.length > 0" value="quotes">
<!-- <i class="ph-quotes ph-bold ph-lg"></i> -->
<span class="count">{{ directQuotes.length }}</span>
{{ i18n.ts._notification._types.quote }}
</option>
<option value="clips" v-if="clips?.length > 0">
<option v-if="clips?.length > 0" value="clips">
<!-- <i class="ph-paperclip ph-bold ph-lg"></i> -->
<span class="count">{{ clips.length }}</span>
{{ i18n.ts.clips }}
@ -64,26 +64,26 @@
</MkTab>
<MkNoteSub
v-if="directReplies && tab === 'replies'"
v-for="note in directReplies"
v-if="directReplies && tab === 'replies'"
:key="note.id"
:note="note"
class="reply"
:conversation="replies"
:detailedView="true"
:parentId="note.id"
:detailed-view="true"
:parent-id="note.id"
/>
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
<MkNoteSub
v-if="directQuotes && tab === 'quotes'"
v-for="note in directQuotes"
v-if="directQuotes && tab === 'quotes'"
:key="note.id"
:note="note"
class="reply"
:conversation="replies"
:detailedView="true"
:parentId="note.id"
:detailed-view="true"
:parent-id="note.id"
/>
<MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" />
@ -94,8 +94,8 @@
:pagination="pagination"
> -->
<MkUserCardMini
v-if="tab === 'renotes' && renotes"
v-for="item in renotes"
v-if="tab === 'renotes' && renotes"
:key="item.user.id"
:user="item.user"
:with-chart="false"
@ -151,11 +151,12 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, onUpdated, ref } from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import type { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
import MkTab from "@/components/MkTab.vue";
import MkNote from "@/components/MkNote.vue";
import MkNoteSub from "@/components/MkNoteSub.vue";
import XRenoteButton from "@/components/MkRenoteButton.vue";
import type XRenoteButton from "@/components/MkRenoteButton.vue";
import MkUserCardMini from "@/components/MkUserCardMini.vue";
import MkReactedUsers from "@/components/MkReactedUsers.vue";
import { pleaseLogin } from "@/scripts/please-login";
@ -170,16 +171,15 @@ import { getNoteMenu } from "@/scripts/get-note-menu";
import { useNoteCapture } from "@/scripts/use-note-capture";
import { deepClone } from "@/scripts/clone";
import { stream } from "@/stream";
import { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
const props = defineProps<{
note: misskey.entities.Note;
pinned?: boolean;
}>();
let tab = ref("replies");
const tab = ref("replies");
let note = ref(deepClone(props.note));
const note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -214,12 +214,12 @@ const muted = ref(
);
const translation = ref(null);
const translating = ref(false);
let conversation = ref<null | misskey.entities.Note[]>([]);
const conversation = ref<null | misskey.entities.Note[]>([]);
const replies = ref<misskey.entities.Note[]>([]);
let directReplies = ref<null | misskey.entities.Note[]>([]);
let directQuotes = ref<null | misskey.entities.Note[]>([]);
let clips = ref();
let renotes = ref();
const directReplies = ref<null | misskey.entities.Note[]>([]);
const directQuotes = ref<null | misskey.entities.Note[]>([]);
const clips = ref();
const renotes = ref();
let isScrolling;
const reactionsCount = Object.values(props.note.reactions).reduce(
@ -238,7 +238,7 @@ const keymap = {
useNoteCapture({
rootEl: el,
note: note,
note,
isDeletedRef: isDeleted,
});
@ -260,7 +260,7 @@ function react(viaKeyboard = false): void {
(reaction) => {
os.api("notes/reactions/create", {
noteId: note.value.id,
reaction: reaction,
reaction,
});
},
() => {

View file

@ -49,7 +49,6 @@
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import type * as misskey from "firefish-js";
import { defaultStore } from "@/store";
import MkVisibility from "@/components/MkVisibility.vue";
@ -63,7 +62,7 @@ const props = defineProps<{
pinned?: boolean;
}>();
let note = ref(props.note);
const note = ref(props.note);
const showTicker =
defaultStore.state.instanceTicker === "always" ||

View file

@ -1,6 +1,6 @@
<template>
<div v-size="{ min: [350, 500] }" class="fefdfafb">
<MkAvatar class="avatar" :user="$i" disableLink />
<MkAvatar class="avatar" :user="$i" disable-link />
<div class="main">
<div class="header">
<MkUserName :user="$i" />
@ -11,7 +11,7 @@
:text="preprocess(text).trim()"
:author="$i"
:i="$i"
advancedMfm
advanced-mfm
/>
</div>
</div>

View file

@ -11,7 +11,7 @@
</template>
<script lang="ts" setup>
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";

View file

@ -1,10 +1,10 @@
<template>
<article
v-if="!muted.muted || muted.what === 'reply'"
:id="detailedView ? appearNote.id : null"
ref="el"
v-size="{ max: [450, 500] }"
class="wrpstxzv"
:id="detailedView ? appearNote.id : null"
tabindex="-1"
:class="{
children: depth > 1,
@ -16,8 +16,8 @@
<div v-if="conversation && depth > 1" class="line"></div>
<div
class="main"
@click="noteClick"
:style="{ cursor: expandOnNoteClick ? 'pointer' : '' }"
@click="noteClick"
>
<div class="avatar-container">
<MkAvatar class="avatar" :user="appearNote.user" />
@ -32,9 +32,9 @@
<MkSubNoteContent
class="text"
:note="note"
:parentId="parentId"
:parent-id="parentId"
:conversation="conversation"
:detailedView="detailedView"
:detailed-view="detailedView"
@focusfooter="footerEl.focus()"
/>
<div v-if="translating || translation" class="translation">
@ -117,9 +117,9 @@
appearNote.myReaction != null
"
ref="reactButton"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
class="button _button reacted"
@click.stop="undoReact(appearNote)"
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
>
<i class="ph-minus ph-bold ph-lg"></i>
</button>
@ -137,17 +137,17 @@
</div>
<template v-if="conversation">
<MkNoteSub
v-if="replyLevel < 11 && depth < 5"
v-for="reply in replies"
v-if="replyLevel < 11 && depth < 5"
:key="reply.id"
:note="reply"
class="reply"
:class="{ single: replies.length == 1 }"
:conversation="conversation"
:depth="replies.length == 1 ? depth : depth + 1"
:replyLevel="replyLevel + 1"
:parentId="appearNote.id"
:detailedView="detailedView"
:reply-level="replyLevel + 1"
:parent-id="appearNote.id"
:detailed-view="detailedView"
/>
<div v-else-if="replies.length > 0" class="more">
<div class="line"></div>
@ -177,9 +177,9 @@
</template>
<script lang="ts" setup>
import { inject, ref, computed } from "vue";
import { computed, inject, ref } from "vue";
import type { Ref } from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
@ -223,7 +223,7 @@ const props = withDefaults(
},
);
let note = ref(deepClone(props.note));
const note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -247,7 +247,7 @@ const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement>();
let appearNote = computed(() =>
const appearNote = computed(() =>
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
);
const isDeleted = ref(false);
@ -291,7 +291,7 @@ function react(viaKeyboard = false): void {
(reaction) => {
os.api("notes/reactions/create", {
noteId: appearNote.value.id,
reaction: reaction,
reaction,
});
},
() => {

View file

@ -12,7 +12,7 @@
</template>
<template #default="{ items: notes }">
<div class="giivymft" :class="{ noGap }" ref="tlEl">
<div ref="tlEl" class="giivymft" :class="{ noGap }">
<XList
ref="notes"
v-slot="{ item: note }"

View file

@ -219,7 +219,7 @@
<MkFollowButton
:user="notification.user"
:full="true"
:hideMenu="true"
:hide-menu="true"
/></div
></span>
<span
@ -273,8 +273,8 @@
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from "vue";
import * as misskey from "firefish-js";
import { onMounted, onUnmounted, ref, watch } from "vue";
import type * as misskey from "firefish-js";
import XReactionIcon from "@/components/MkReactionIcon.vue";
import MkFollowButton from "@/components/MkFollowButton.vue";
import XReactionTooltip from "@/components/MkReactionTooltip.vue";
@ -310,8 +310,7 @@ const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReact
? instance.defaultReaction
: "⭐";
let readObserver: IntersectionObserver | undefined;
let connection;
let readObserver: IntersectionObserver | undefined, connection;
onMounted(() => {
if (!props.notification.isRead) {

View file

@ -41,7 +41,6 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import {} from "vue";
import { notificationTypes } from "firefish-js";
import MkSwitch from "./form/switch.vue";
import MkInfo from "./MkInfo.vue";
@ -65,12 +64,12 @@ const props = withDefaults(
},
);
let includingTypes = computed(() => props.includingTypes || []);
const includingTypes = computed(() => props.includingTypes || []);
const dialog = ref<InstanceType<typeof XModalWindow>>();
let typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
let useGlobalSetting = ref(
const typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
const useGlobalSetting = ref(
(includingTypes.value === null || includingTypes.value.length === 0) &&
props.showGlobalToggle,
);

View file

@ -28,7 +28,7 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex("high");
let showing = ref(true);
const showing = ref(true);
onMounted(() => {
window.setTimeout(() => {

View file

@ -26,7 +26,7 @@
"
:key="notification.id"
:note="notification.note"
:collapsedReply="
:collapsed-reply="
notification.type === 'reply' ||
(notification.type === 'mention' &&
notification.note.replyId != null)
@ -46,9 +46,10 @@
</template>
<script lang="ts" setup>
import { onUnmounted, onMounted, computed, ref } from "vue";
import { notificationTypes } from "firefish-js";
import MkPagination, { Paging } from "@/components/MkPagination.vue";
import { computed, onMounted, onUnmounted, ref } from "vue";
import type { notificationTypes } from "firefish-js";
import type { Paging } from "@/components/MkPagination.vue";
import MkPagination from "@/components/MkPagination.vue";
import XNotification from "@/components/MkNotification.vue";
import XList from "@/components/MkDateSeparatedList.vue";
import XNote from "@/components/MkNote.vue";

View file

@ -8,8 +8,8 @@
:buttons-left="buttonsLeft"
:buttons-right="buttonsRight"
:contextmenu="contextmenu"
@closed="$emit('closed')"
class="page-window"
@closed="$emit('closed')"
>
<template #header>
<template v-if="pageMetadata?.value">
@ -30,7 +30,8 @@
</template>
<script lang="ts" setup>
import { ComputedRef, provide, ref, computed } from "vue";
import type { ComputedRef } from "vue";
import { computed, provide, ref } from "vue";
import RouterView from "@/components/global/RouterView.vue";
import XWindow from "@/components/MkWindow.vue";
import { popout as _popout } from "@/scripts/popout";
@ -39,7 +40,8 @@ import { url } from "@/config";
import { mainRouter, routes } from "@/router";
import { Router } from "@/nirax";
import { i18n } from "@/i18n";
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
import type { PageMetadata } from "@/scripts/page-metadata";
import { provideMetadataReceiver } from "@/scripts/page-metadata";
const props = defineProps<{
initialPath: string;
@ -51,8 +53,8 @@ defineEmits<{
const router = new Router(routes, props.initialPath);
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let windowEl = ref<InstanceType<typeof XWindow>>();
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
const windowEl = ref<InstanceType<typeof XWindow>>();
const history = ref<{ path: string; key: any }[]>([
{
path: router.getCurrentPath(),

View file

@ -63,29 +63,22 @@
</template>
<script lang="ts" setup>
import {
computed,
ComputedRef,
isRef,
onActivated,
onDeactivated,
ref,
watch,
} from "vue";
import * as misskey from "firefish-js";
import type { ComputedRef } from "vue";
import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
import type * as misskey from "firefish-js";
import * as os from "@/os";
import {
onScrollTop,
isTopVisible,
getScrollPosition,
getScrollContainer,
getScrollPosition,
isTopVisible,
onScrollTop,
} from "@/scripts/scroll";
import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n";
export type Paging<
export interface Paging<
E extends keyof misskey.Endpoints = keyof misskey.Endpoints,
> = {
> {
endpoint: E;
limit: number;
params?:
@ -104,7 +97,7 @@ export type Paging<
reversed?: boolean;
offsetMode?: boolean;
};
}
const SECOND_FETCH_LIMIT = 30;
@ -123,7 +116,10 @@ const emit = defineEmits<{
(ev: "queue", count: number): void;
}>();
type Item = { id: string; [another: string]: unknown };
interface Item {
id: string;
[another: string]: unknown;
}
const rootEl = ref<HTMLElement>();
const items = ref<Item[]>([]);
@ -207,12 +203,12 @@ const refresh = async (): void => {
})
.then(
(res) => {
let ids = items.value.reduce(
const ids = items.value.reduce(
(a, b) => {
a[b.id] = true;
return a;
},
{} as { [id: string]: boolean },
{} as Record<string, boolean>,
);
for (let i = 0; i < res.length; i++) {
@ -364,7 +360,7 @@ const prepend = (item: Item): void => {
//
if (items.value.length >= props.displayLimit) {
// Vue 3.2
//items.value = items.value.slice(-props.displayLimit);
// items.value = items.value.slice(-props.displayLimit);
while (items.value.length >= props.displayLimit) {
items.value.shift();
}
@ -394,7 +390,7 @@ const prepend = (item: Item): void => {
//
if (items.value.length >= props.displayLimit) {
// Vue 3.2
//this.items = items.value.slice(0, props.displayLimit);
// this.items = items.value.slice(0, props.displayLimit);
while (items.value.length >= props.displayLimit) {
items.value.pop();
}

View file

@ -53,7 +53,7 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import { sum } from "@/scripts/array";
import { pleaseLogin } from "@/scripts/please-login";
import * as os from "@/os";

View file

@ -16,8 +16,8 @@
</MkInput>
<button
class="_button"
@click="remove(i)"
:aria-label="i18n.t('remove')"
@click="remove(i)"
>
<i class="ph-x ph-bold ph-lg"></i>
</button>

View file

@ -5,9 +5,9 @@
:z-priority="'high'"
:src="src"
:transparent-bg="true"
tabindex="-1"
@click="modal?.close()"
@closed="emit('closed')"
tabindex="-1"
>
<MkMenu
:items="items"
@ -28,7 +28,7 @@ import { ref } from "vue";
import MkModal from "./MkModal.vue";
import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu";
import type { MenuItem } from "@/types/menu";
defineProps<{
items: MenuItem[];
@ -43,7 +43,7 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let modal = ref<InstanceType<typeof MkModal>>();
const modal = ref<InstanceType<typeof MkModal>>();
</script>
<style lang="scss" scoped>

View file

@ -86,8 +86,8 @@
{{ i18n.ts.quoteAttached
}}<button
class="_button"
@click="quoteId = null"
:aria-label="i18n.t('removeQuote')"
@click="quoteId = null"
>
<i class="ph-x ph-bold ph-lg"></i>
</button>
@ -99,8 +99,8 @@
<MkAcct :user="u" />
<button
class="_button"
@click="removeVisibleUser(u)"
:aria-label="i18n.t('removeRecipient')"
@click="removeVisibleUser(u)"
>
<i class="ph-x ph-bold ph-lg"></i>
</button>
@ -234,16 +234,16 @@
<script lang="ts" setup>
import {
computed,
defineAsyncComponent,
inject,
watch,
nextTick,
onMounted,
defineAsyncComponent,
ref,
computed,
watch,
} from "vue";
import * as mfm from "mfm-js";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import autosize from "autosize";
import insertTextAtCursor from "insert-text-at-cursor";
import { length } from "stringz";
@ -315,40 +315,42 @@ const cwInputEl = ref<HTMLInputElement | null>(null);
const hashtagsInputEl = ref<HTMLInputElement | null>(null);
const visibilityButton = ref<HTMLElement | null>(null);
let posting = ref(false);
let text = ref(props.initialText ?? "");
let files = ref(props.initialFiles ?? []);
let poll = ref<{
const posting = ref(false);
const text = ref(props.initialText ?? "");
const files = ref(props.initialFiles ?? []);
const poll = ref<{
choices: string[];
multiple: boolean;
expiresAt: string | null;
expiredAfter: string | null;
} | null>(null);
let useCw = ref(false);
let showPreview = ref(false);
let cw = ref<string | null>(null);
let localOnly = ref<boolean>(
const useCw = ref(false);
const showPreview = ref(false);
const cw = ref<string | null>(null);
const localOnly = ref<boolean>(
props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility
? defaultStore.state.localOnly
: defaultStore.state.defaultNoteLocalOnly,
);
let visibility = ref(
const visibility = ref(
props.initialVisibility ??
((defaultStore.state.rememberNoteVisibility
? defaultStore.state.visibility
: defaultStore.state
.defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]),
);
let visibleUsers = ref([]);
const visibleUsers = ref([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
let autocomplete = ref(null);
let draghover = ref(false);
let quoteId = ref(null);
let hasNotSpecifiedMentions = ref(false);
let recentHashtags = ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
let imeText = ref("");
const autocomplete = ref(null);
const draghover = ref(false);
const quoteId = ref(null);
const hasNotSpecifiedMentions = ref(false);
const recentHashtags = ref(
JSON.parse(localStorage.getItem("hashtags") || "[]"),
);
const imeText = ref("");
const typing = throttle(3000, () => {
if (props.channel) {
@ -415,8 +417,8 @@ const maxTextLength = computed((): number => {
const canPost = computed((): boolean => {
return (
!posting.value &&
(1 <= textLength.value ||
1 <= files.value.length ||
(textLength.value >= 1 ||
files.value.length >= 1 ||
!!poll.value ||
!!props.renote) &&
textLength.value <= maxTextLength.value &&
@ -816,14 +818,14 @@ function onDrop(ev): void {
return;
}
//#region
// #region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile);
files.value.push(file);
ev.preventDefault();
}
//#endregion
// #endregion
}
function saveDraft() {
@ -896,7 +898,7 @@ async function post() {
}
}
let token = undefined;
let token;
if (postAccount.value) {
const storedAccounts = await getAccounts();
@ -976,7 +978,7 @@ function showActions(ev) {
);
}
let postAccount = ref<misskey.entities.UserDetailed | null>(null);
const postAccount = ref<misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_(

View file

@ -8,9 +8,9 @@
delay-on-touch-only="true"
>
<div
class="file"
v-for="element in _files"
:key="element.id"
class="file"
@click="showFileMenu(element, $event)"
@contextmenu.prevent="showFileMenu(element, $event)"
>
@ -30,7 +30,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref, computed } from "vue";
import { computed, defineAsyncComponent, ref } from "vue";
import { VueDraggable } from "vue-draggable-plus";
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
import * as os from "@/os";
@ -105,10 +105,11 @@ async function describe(file) {
{
done: (result) => {
if (!result || result.canceled) return;
let comment = result.result.length === 0 ? null : result.result;
const comment =
result.result.length === 0 ? null : result.result;
os.api("drive/files/update", {
fileId: file.id,
comment: comment,
comment,
}).then(() => {
file.comment = comment;
});

View file

@ -21,8 +21,7 @@
<script lang="ts" setup>
import { shallowRef } from "vue";
import {} from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import MkModal from "@/components/MkModal.vue";
import MkPostForm from "@/components/MkPostForm.vue";
@ -48,8 +47,8 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let modal = shallowRef<InstanceType<typeof MkModal>>();
let form = shallowRef<InstanceType<typeof MkPostForm>>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const form = shallowRef<InstanceType<typeof MkPostForm>>();
function onPosted() {
modal.value.close({

View file

@ -76,12 +76,12 @@ defineProps<{
}>();
// ServiceWorker registration
let registration = ref<ServiceWorkerRegistration | undefined>();
const registration = ref<ServiceWorkerRegistration | undefined>();
// If this browser supports push notification
let supported = ref(false);
const supported = ref(false);
// If this browser has already subscribed to push notification
let pushSubscription = ref<PushSubscription | null>(null);
let pushRegistrationInServer = ref<
const pushSubscription = ref<PushSubscription | null>(null);
const pushRegistrationInServer = ref<
| {
state?: string;
key?: string;
@ -209,6 +209,6 @@ if (navigator.serviceWorker == null) {
}
defineExpose({
pushRegistrationInServer: pushRegistrationInServer,
pushRegistrationInServer,
});
</script>

View file

@ -36,8 +36,8 @@
</template>
<script lang="ts" setup>
import { onMounted, watch, ref } from "vue";
import * as misskey from "firefish-js";
import { onMounted, ref, watch } from "vue";
import type * as misskey from "firefish-js";
import MkReactionIcon from "@/components/MkReactionIcon.vue";
import MkUserCardMini from "@/components/MkUserCardMini.vue";
import * as os from "@/os";
@ -46,10 +46,10 @@ const props = defineProps<{
noteId: misskey.entities.Note["id"];
}>();
let note = ref<misskey.entities.Note>();
let tab = ref<string>();
let reactions = ref<string[]>();
let users = ref();
const note = ref<misskey.entities.Note>();
const tab = ref<string>();
const reactions = ref<string[]>();
const users = ref();
watch(tab, async () => {
const res = await os.api("notes/reactions", {

View file

@ -22,7 +22,7 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import XDetails from "@/components/MkReactionsViewer.details.vue";
import XReactionIcon from "@/components/MkReactionIcon.vue";
import * as os from "@/os";

View file

@ -18,7 +18,7 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import * as misskey from "firefish-js";
import type * as misskey from "firefish-js";
import { $i } from "@/account";
import XReaction from "@/components/MkReactionsViewer.reaction.vue";

Some files were not shown because too many files have changed in this diff Show more