@{user.acct}
-
+
-
+
{user.fields.map((data, index) => (
- {data.name}
diff --git a/renderer/components/timelines/status/Body.tsx b/renderer/components/timelines/status/Body.tsx
index 50572bb3..63a7ad26 100644
--- a/renderer/components/timelines/status/Body.tsx
+++ b/renderer/components/timelines/status/Body.tsx
@@ -8,6 +8,7 @@ type Props = {
status: Entity.Status
spoilered: boolean
setSpoilered: Dispatch>
+ onClick?: (e: any) => void
} & HTMLAttributes
export default function Body(props: Props) {
@@ -41,6 +42,7 @@ export default function Body(props: Props) {
className={`${props.className} raw-html`}
style={Object.assign({ wordWrap: 'break-word' }, props.style)}
dangerouslySetInnerHTML={{ __html: emojify(props.status.content, props.status.emojis) }}
+ onClick={props.onClick}
/>
)}
>
diff --git a/renderer/components/timelines/status/Status.tsx b/renderer/components/timelines/status/Status.tsx
index 2d0a5b37..75154a40 100644
--- a/renderer/components/timelines/status/Status.tsx
+++ b/renderer/components/timelines/status/Status.tsx
@@ -9,7 +9,8 @@ import Poll from './Poll'
import { FormattedMessage } from 'react-intl'
import Actions from './Actions'
import { useRouter } from 'next/router'
-import { useState } from 'react'
+import { MouseEventHandler, useState } from 'react'
+import { findLink } from '@/utils/statusParser'
type Props = {
status: Entity.Status
@@ -36,6 +37,15 @@ export default function Status(props: Props) {
router.push({ query: { id: router.query.id, timeline: router.query.timeline, user_id: id, detail: true } })
}
+ const statusClicked: MouseEventHandler = async e => {
+ const url = findLink(e.target as HTMLElement, 'status-body')
+ if (url) {
+ global.ipc.invoke('open-browser', url)
+ e.preventDefault()
+ e.stopPropagation()
+ }
+ }
+
return (
{rebloggedHeader(props.status)}
@@ -56,7 +66,9 @@ export default function Status(props: Props) {
-
+
+
+
{!spoilered && (
<>
{status.poll &&
}
diff --git a/renderer/utils/statusParser.ts b/renderer/utils/statusParser.ts
new file mode 100644
index 00000000..930d4aa3
--- /dev/null
+++ b/renderer/utils/statusParser.ts
@@ -0,0 +1,129 @@
+import { Entity } from 'megalodon'
+
+export type ParsedAccount = {
+ username: string
+ acct: string
+ url: string
+}
+
+export function findLink(target: HTMLElement | null, parentClassName: string): string | null {
+ if (!target) {
+ return null
+ }
+ if (target.localName === 'a') {
+ return (target as HTMLLinkElement).href
+ }
+ if (target.parentNode === undefined || target.parentNode === null) {
+ return null
+ }
+ const parent = target.parentNode as HTMLElement
+ if (parent.getAttribute && parent.getAttribute('class') === parentClassName) {
+ return null
+ }
+ return findLink(parent, parentClassName)
+}
+
+export function findTag(target: HTMLElement, parentClass = 'toot'): string | null {
+ if (!target || !target.getAttribute) {
+ return null
+ }
+ const targetClass = target.getAttribute('class')
+ if (targetClass && targetClass.includes('hashtag')) {
+ return parseTag((target as HTMLLinkElement).href)
+ }
+ // In Pleroma, link does not have class.
+ // So I have to check URL.
+ const link = target as HTMLLinkElement
+ if (link.href && link.href.match(/^https:\/\/[a-zA-Z0-9-.]+\/(tag|tags)\/.+/)) {
+ return parseTag(link.href)
+ }
+ if (target.parentNode === undefined || target.parentNode === null) {
+ return null
+ }
+ const parent = target.parentNode as HTMLElement
+ if (parent.getAttribute && parent.getAttribute('class') === parentClass) {
+ return null
+ }
+ return findTag(parent, parentClass)
+}
+
+function parseTag(tagURL: string): string | null {
+ const res = tagURL.match(/^https:\/\/([a-zA-Z0-9-.]+)\/(tag|tags)\/(.+)/)
+ if (!res) {
+ return null
+ }
+ return res[3]
+}
+
+export function findAccount(target: HTMLElement | null, parentClassName: string): ParsedAccount | null {
+ if (!target || !target.getAttribute) {
+ return null
+ }
+
+ const targetClass = target.getAttribute('class')
+ const link = target as HTMLLinkElement
+ if (targetClass && targetClass.includes('u-url')) {
+ if (link.href && link.href.match(/^https:\/\/[a-zA-Z0-9-.]+\/users\/[a-zA-Z0-9-_.]+$/)) {
+ return parsePleromaAccount(link.href)
+ } else {
+ return parseMastodonAccount(link.href)
+ }
+ }
+ // In Pleroma, link does not have class.
+ // So we have to check URL.
+ if (link.href && link.href.match(/^https:\/\/[a-zA-Z0-9-.]+\/@[a-zA-Z0-9-_.]+$/)) {
+ return parseMastodonAccount(link.href)
+ }
+ // Toot URL of Pleroma does not contain @.
+ if (link.href && link.href.match(/^https:\/\/[a-zA-Z0-9-.]+\/users\/[a-zA-Z0-9-_.]+$/)) {
+ return parsePleromaAccount(link.href)
+ }
+ if (target.parentNode === undefined || target.parentNode === null) {
+ return null
+ }
+ const parent = target.parentNode as HTMLElement
+ if (parent.getAttribute && parent.getAttribute('class') === parentClassName) {
+ return null
+ }
+ return findAccount(parent, parentClassName)
+}
+
+export function parseMastodonAccount(accountURL: string): ParsedAccount | null {
+ const res = accountURL.match(/^https:\/\/([a-zA-Z0-9-.]+)\/(@[a-zA-Z0-9-_.]+)$/)
+ if (!res) {
+ return null
+ }
+ const domainName = res[1]
+ const accountName = res[2]
+ return {
+ username: accountName,
+ acct: `${accountName}@${domainName}`,
+ url: accountURL
+ }
+}
+
+export function parsePleromaAccount(accountURL: string): ParsedAccount | null {
+ const res = accountURL.match(/^https:\/\/([a-zA-Z0-9-.]+)\/users\/([a-zA-Z0-9-_.]+)$/)
+ if (!res) {
+ return null
+ }
+ const domainName = res[1]
+ const accountName = res[2]
+ return {
+ username: `@${accountName}`,
+ acct: `@${accountName}@${domainName}`,
+ url: accountURL
+ }
+}
+
+export function accountMatch(findAccounts: Array
, parsedAccount: ParsedAccount, domain: string): Entity.Account | false {
+ const account = findAccounts.find(a => `@${a.acct}` === parsedAccount.acct)
+ if (account) return account
+ const pleromaUser = findAccounts.find(a => a.acct === parsedAccount.acct)
+ if (pleromaUser) return pleromaUser
+ const localUser = findAccounts.find(a => `@${a.username}@${domain}` === parsedAccount.acct)
+ if (localUser) return localUser
+ const user = findAccounts.find(a => a.url === parsedAccount.url)
+ if (!user) return false
+ return user
+}