0.8.3.0
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -707,6 +707,7 @@ const D3OrgChartFlowComponent = forwardRef<D3OrgChartRef, D3OrgChartFlowProps>(
|
|||||||
">
|
">
|
||||||
${sp.birthYear}${sp.deathYear ? `-${sp.deathYear}` : '-'}
|
${sp.birthYear}${sp.deathYear ? `-${sp.deathYear}` : '-'}
|
||||||
</div>
|
</div>
|
||||||
|
${sp.ageText ? `<div style="font-size: 9px; color: #000000; margin-top: 2px; font-weight: 500; font-family: serif;">${sp.ageText}</div>` : ''}
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -214,9 +214,6 @@ function PersonCard({
|
|||||||
|
|
||||||
// 是否显示右键菜单(有可添加的选项时才显示)
|
// 是否显示右键菜单(有可添加的选项时才显示)
|
||||||
const hasAddOptions = canEdit && (canAddFather || canAddMother || canAddSpouse || canAddChild)
|
const hasAddOptions = canEdit && (canAddFather || canAddMother || canAddSpouse || canAddChild)
|
||||||
|
|
||||||
// 调试日志
|
|
||||||
console.log('PersonCard render:', member.fullName, { canEdit, hasAddOptions, relationMode })
|
|
||||||
|
|
||||||
// 庄严正式的中国风配色
|
// 庄严正式的中国风配色
|
||||||
const isDead = !!member.deathDate
|
const isDead = !!member.deathDate
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export function TreeLayout({
|
|||||||
const VERTICAL_GAP = 48 // 3rem = 48px
|
const VERTICAL_GAP = 48 // 3rem = 48px
|
||||||
const CONNECTOR_HEIGHT = 48 // 连接线垂直高度
|
const CONNECTOR_HEIGHT = 48 // 连接线垂直高度
|
||||||
|
|
||||||
|
// 节点注册计数,用于触发连线重新计算
|
||||||
|
const [nodeRegisteredCount, setNodeRegisteredCount] = useState(0)
|
||||||
|
|
||||||
// 折叠状态管理:存储被折叠的节点ID
|
// 折叠状态管理:存储被折叠的节点ID
|
||||||
const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(() => new Set())
|
const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(() => new Set())
|
||||||
|
|
||||||
@@ -81,6 +84,8 @@ export function TreeLayout({
|
|||||||
const handleNodeRef = useCallback((memberId: string, el: HTMLDivElement | null) => {
|
const handleNodeRef = useCallback((memberId: string, el: HTMLDivElement | null) => {
|
||||||
if (el) {
|
if (el) {
|
||||||
nodeRefs.current.set(memberId, el)
|
nodeRefs.current.set(memberId, el)
|
||||||
|
// 触发连线重新计算
|
||||||
|
setNodeRegisteredCount(prev => prev + 1)
|
||||||
} else {
|
} else {
|
||||||
nodeRefs.current.delete(memberId)
|
nodeRefs.current.delete(memberId)
|
||||||
}
|
}
|
||||||
@@ -102,10 +107,14 @@ export function TreeLayout({
|
|||||||
}, [relationMode, onMemberClick, router])
|
}, [relationMode, onMemberClick, router])
|
||||||
|
|
||||||
// 检查节点是否被任何祖先折叠(使用闭包避免递归依赖问题)
|
// 检查节点是否被任何祖先折叠(使用闭包避免递归依赖问题)
|
||||||
|
// 注意:这个函数用于判断节点是否应该被隐藏,不用于连线计算
|
||||||
const isNodeHiddenByCollapse = useCallback((nodeId: string): boolean => {
|
const isNodeHiddenByCollapse = useCallback((nodeId: string): boolean => {
|
||||||
|
// 如果没有折叠的节点,直接返回 false
|
||||||
|
if (collapsedNodes.size === 0) return false
|
||||||
|
|
||||||
const checkHidden = (id: string): boolean => {
|
const checkHidden = (id: string): boolean => {
|
||||||
const member = getMember(id)
|
const member = getMember(id)
|
||||||
if (!member) return true
|
if (!member) return false // 找不到成员时返回 false,不隐藏
|
||||||
|
|
||||||
// 检查父节点是否被折叠
|
// 检查父节点是否被折叠
|
||||||
const parentId = member.fatherId || member.motherId
|
const parentId = member.fatherId || member.motherId
|
||||||
@@ -123,26 +132,54 @@ export function TreeLayout({
|
|||||||
const calculateConnections = useCallback(() => {
|
const calculateConnections = useCallback(() => {
|
||||||
const newConnections: Connection[] = []
|
const newConnections: Connection[] = []
|
||||||
const containerRect = containerRef.current?.getBoundingClientRect()
|
const containerRect = containerRef.current?.getBoundingClientRect()
|
||||||
if (!containerRect) return
|
if (!containerRect) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否有足够的节点已注册
|
// 检查是否有足够的节点已注册
|
||||||
if (nodeRefs.current.size === 0) return
|
if (nodeRefs.current.size === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let processedCount = 0
|
||||||
|
let skippedNoChildren = 0
|
||||||
|
let skippedCollapsed = 0
|
||||||
|
let skippedHidden = 0
|
||||||
|
let skippedNotInDom = 0
|
||||||
|
let skippedZeroSize = 0
|
||||||
|
|
||||||
nodeRefs.current.forEach((nodeElement, nodeId) => {
|
nodeRefs.current.forEach((nodeElement, nodeId) => {
|
||||||
const member = getMember(nodeId)
|
const member = getMember(nodeId)
|
||||||
if (!member || !member.childrenIds || member.childrenIds.length === 0) return
|
if (!member || !member.childrenIds || member.childrenIds.length === 0) {
|
||||||
|
skippedNoChildren++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 如果该节点被折叠,不绘制到子节点的连线
|
// 如果该节点被折叠,不绘制到子节点的连线
|
||||||
if (collapsedNodes.has(nodeId)) return
|
if (collapsedNodes.has(nodeId)) {
|
||||||
|
skippedCollapsed++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 如果该节点被祖先折叠隐藏,跳过
|
// 如果该节点被祖先折叠隐藏,跳过
|
||||||
if (isNodeHiddenByCollapse(nodeId)) return
|
if (isNodeHiddenByCollapse(nodeId)) {
|
||||||
|
skippedHidden++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 检查元素是否仍在 DOM 中且可见
|
// 检查元素是否仍在 DOM 中且可见
|
||||||
if (!document.body.contains(nodeElement)) return
|
if (!document.body.contains(nodeElement)) {
|
||||||
|
skippedNotInDom++
|
||||||
|
return
|
||||||
|
}
|
||||||
const parentRect = nodeElement.getBoundingClientRect()
|
const parentRect = nodeElement.getBoundingClientRect()
|
||||||
// 如果元素不可见(宽高为0),跳过
|
// 如果元素不可见(宽高为0),跳过
|
||||||
if (parentRect.width === 0 || parentRect.height === 0) return
|
if (parentRect.width === 0 || parentRect.height === 0) {
|
||||||
|
skippedZeroSize++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processedCount++
|
||||||
|
|
||||||
// 考虑缩放因素:getBoundingClientRect 返回的是缩放后的尺寸
|
// 考虑缩放因素:getBoundingClientRect 返回的是缩放后的尺寸
|
||||||
// 需要除以 scale 来获取实际的逻辑位置
|
// 需要除以 scale 来获取实际的逻辑位置
|
||||||
@@ -209,6 +246,8 @@ export function TreeLayout({
|
|||||||
timers.push(setTimeout(calculateConnections, 50))
|
timers.push(setTimeout(calculateConnections, 50))
|
||||||
timers.push(setTimeout(calculateConnections, 150))
|
timers.push(setTimeout(calculateConnections, 150))
|
||||||
timers.push(setTimeout(calculateConnections, 300))
|
timers.push(setTimeout(calculateConnections, 300))
|
||||||
|
timers.push(setTimeout(calculateConnections, 500))
|
||||||
|
timers.push(setTimeout(calculateConnections, 1000))
|
||||||
return () => timers.forEach(timer => clearTimeout(timer))
|
return () => timers.forEach(timer => clearTimeout(timer))
|
||||||
}, [collapsedNodes, calculateConnections])
|
}, [collapsedNodes, calculateConnections])
|
||||||
|
|
||||||
@@ -217,13 +256,24 @@ export function TreeLayout({
|
|||||||
calculateConnections()
|
calculateConnections()
|
||||||
}, [scale, calculateConnections])
|
}, [scale, calculateConnections])
|
||||||
|
|
||||||
|
// 节点注册变化时重新计算连线(防抖)
|
||||||
|
useEffect(() => {
|
||||||
|
if (nodeRegisteredCount === 0) return
|
||||||
|
const timer = setTimeout(calculateConnections, 100)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [nodeRegisteredCount, calculateConnections])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 多次尝试计算,确保 DOM 完全渲染
|
// 多次尝试计算,确保 DOM 完全渲染
|
||||||
|
// 对于大型族谱(100+人),需要更长的延迟
|
||||||
const timers: NodeJS.Timeout[] = []
|
const timers: NodeJS.Timeout[] = []
|
||||||
timers.push(setTimeout(calculateConnections, 50))
|
timers.push(setTimeout(calculateConnections, 100))
|
||||||
timers.push(setTimeout(calculateConnections, 150))
|
|
||||||
timers.push(setTimeout(calculateConnections, 300))
|
timers.push(setTimeout(calculateConnections, 300))
|
||||||
timers.push(setTimeout(calculateConnections, 500))
|
timers.push(setTimeout(calculateConnections, 500))
|
||||||
|
timers.push(setTimeout(calculateConnections, 1000))
|
||||||
|
timers.push(setTimeout(calculateConnections, 2000))
|
||||||
|
timers.push(setTimeout(calculateConnections, 3000))
|
||||||
|
timers.push(setTimeout(calculateConnections, 5000))
|
||||||
|
|
||||||
// 监听窗口大小变化
|
// 监听窗口大小变化
|
||||||
window.addEventListener('resize', calculateConnections)
|
window.addEventListener('resize', calculateConnections)
|
||||||
@@ -395,7 +445,7 @@ export function TreeLayout({
|
|||||||
top: `${top}px`,
|
top: `${top}px`,
|
||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
height: `${height}px`,
|
height: `${height}px`,
|
||||||
backgroundColor: 'rgba(180, 83, 9, 0.7)', // amber-700 with 70% opacity
|
backgroundColor: 'rgba(180, 83, 9, 0.7)',
|
||||||
transform: isHorizontal ? 'scaleY(0.5)' : 'scaleX(0.5)',
|
transform: isHorizontal ? 'scaleY(0.5)' : 'scaleX(0.5)',
|
||||||
transformOrigin: 'top left',
|
transformOrigin: 'top left',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ rsync -avz --progress --delete \
|
|||||||
--exclude 'deploy-manual.sh' \
|
--exclude 'deploy-manual.sh' \
|
||||||
--exclude 'setup-ssh-key.sh' \
|
--exclude 'setup-ssh-key.sh' \
|
||||||
--exclude '*.tar.gz' \
|
--exclude '*.tar.gz' \
|
||||||
|
--exclude 'public/uploads' \
|
||||||
-e "ssh" \
|
-e "ssh" \
|
||||||
./ $SSH_HOST:$REMOTE_DIR/
|
./ $SSH_HOST:$REMOTE_DIR/
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
+1
-1
@@ -762,7 +762,7 @@ prunedAt: Sun, 21 Dec 2025 04:00:03 GMT
|
|||||||
publicHoistPattern: []
|
publicHoistPattern: []
|
||||||
registries:
|
registries:
|
||||||
'@jsr': https://npm.jsr.io/
|
'@jsr': https://npm.jsr.io/
|
||||||
default: https://registry.npmmirror.com/
|
default: https://registry.npmjs.org/
|
||||||
skipped:
|
skipped:
|
||||||
- '@emnapi/runtime@1.7.1'
|
- '@emnapi/runtime@1.7.1'
|
||||||
- '@esbuild/aix-ppc64@0.25.12'
|
- '@esbuild/aix-ppc64@0.25.12'
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"lastValidatedTimestamp": 1766317151581,
|
"lastValidatedTimestamp": 1766327831114,
|
||||||
"projects": {},
|
"projects": {},
|
||||||
"pnpmfiles": [],
|
"pnpmfiles": [],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
|
||||||
|
NhAAAAAwEAAQAAAgEAzTkfu+clcv5NnQ4ubyu2TGJaPGv8+I9/Q4t0E6U1wIGWqeR2PNG4
|
||||||
|
ePZvRHQlsRcBlNN1da/xvCLkwY9hx5SVngESc0c451QWr4KlKgzcj3LzIXie1NYYsKxujV
|
||||||
|
Lty5pOMSU2FWlrhlQ8o7W8hOQ6Jr+sb3cZ7dR8daVGgVWcaIvjXDsMcuPp6JROcIAA53HY
|
||||||
|
xfJjY54Maw7WZS14uyKxy9cdTrGIhJw+i6xl+/psZ1o0THFexMoqbBsFYIDNfxXaDgtrXV
|
||||||
|
BIlSBllsPZUv5QxGVH7ojuXPF8auENp4bGp3u1OPoJm4d/HxstBRCtfPcCxaKHYLBh8Okt
|
||||||
|
+mvIr6UH5BvP92souOyOO4qa5QbZ68HCV9CY/VazFi9HnfLZ5w53Xaqtb0ZBhtlPpfKox7
|
||||||
|
NpTh1uPMZI1in3i+9G3doz7qF3NDzJVDCI4tpSwiuxL4sJff8fflcs58DeA9olpnfpgS9t
|
||||||
|
4XUuZEGCSChU0MmTs1t1vQHbZbQd2wlofvS9+eFvhxTOpK3+0ihRvW+axE0UeswJzEoq0l
|
||||||
|
ZPAcGJZQV5p33ERv7VktE78BSX3DQJBBFamrfVvkjn93/RytrcKzF94Xf4khanfxiSPuEq
|
||||||
|
ap7O8/MmaCobDP2oq61xPx6PCuA7wcRIEuN3tndxp43oSvkJjo66YPYqiUscHc1xZhXkae
|
||||||
|
EAAAdQRZWcH0WVnB8AAAAHc3NoLXJzYQAAAgEAzTkfu+clcv5NnQ4ubyu2TGJaPGv8+I9/
|
||||||
|
Q4t0E6U1wIGWqeR2PNG4ePZvRHQlsRcBlNN1da/xvCLkwY9hx5SVngESc0c451QWr4KlKg
|
||||||
|
zcj3LzIXie1NYYsKxujVLty5pOMSU2FWlrhlQ8o7W8hOQ6Jr+sb3cZ7dR8daVGgVWcaIvj
|
||||||
|
XDsMcuPp6JROcIAA53HYxfJjY54Maw7WZS14uyKxy9cdTrGIhJw+i6xl+/psZ1o0THFexM
|
||||||
|
oqbBsFYIDNfxXaDgtrXVBIlSBllsPZUv5QxGVH7ojuXPF8auENp4bGp3u1OPoJm4d/Hxst
|
||||||
|
BRCtfPcCxaKHYLBh8Okt+mvIr6UH5BvP92souOyOO4qa5QbZ68HCV9CY/VazFi9HnfLZ5w
|
||||||
|
53Xaqtb0ZBhtlPpfKox7NpTh1uPMZI1in3i+9G3doz7qF3NDzJVDCI4tpSwiuxL4sJff8f
|
||||||
|
flcs58DeA9olpnfpgS9t4XUuZEGCSChU0MmTs1t1vQHbZbQd2wlofvS9+eFvhxTOpK3+0i
|
||||||
|
hRvW+axE0UeswJzEoq0lZPAcGJZQV5p33ERv7VktE78BSX3DQJBBFamrfVvkjn93/Rytrc
|
||||||
|
KzF94Xf4khanfxiSPuEqap7O8/MmaCobDP2oq61xPx6PCuA7wcRIEuN3tndxp43oSvkJjo
|
||||||
|
66YPYqiUscHc1xZhXkaeEAAAADAQABAAACAQDACJ0OSjv7v8TGmveOZXvfPAUuFuqPeE9g
|
||||||
|
2ARVQbnrmhdugG63eJNC9W6mwnxmmp2LMtftuSbdolUmXlHj3MCoKl6malXv+PqFXx3IHG
|
||||||
|
LjBHBHuMP/axuNbrzAF4KWi5xxVl2maZAJEZfwpOV0AM/9ZEwpvWwQ5U1VFMPF0GNcXNHy
|
||||||
|
gvqiI2zEispfcRfnetuaVrb0B3edUjQCVytrCQsQMAmnkm6pkD7imf6QbAO2Lm1Tvp4sTP
|
||||||
|
SnVh2Q8NXZ/oudqsfbeH2Ctd751eftRjYzoIZPyegqJnoC1Pbe9Tm8jQDGAQquc3rbk6Vr
|
||||||
|
4PBGSCla4DwHmOT89mVkuCkBWs7m0ZFPgeGcsEiWC0RYv+woqWO2zpQjfHCywBzDUgyUUc
|
||||||
|
xo1ZQ/nMlTicY9E7mO0irBWPwiHpkEHcrLRcIhhm0oPDWxyABEBCSCi0ogdzhZDjLUQ8Cw
|
||||||
|
3upqw6hZL+jP1svUGmOGMucg6f0BY68GJldlL3sa8pKU/pASTiHzXaVZjg+yOqo5MNNtdV
|
||||||
|
Tx+UkkAqDED068WA3652VaiTvD9p880ygGHxZqdHO/AavYvQ3B5ncx1SGLrDPBKjXJFz8r
|
||||||
|
Xlanh0CEJA0liaY7BzIL+D9IyounHtub/32ASoFRhdR6MJ/9BDDP0slE/qKhUbN7FPr6bW
|
||||||
|
7dvZqD6y9BwvlVjYy9cQAAAQEA0LyYBghU1ZrM5BHG9B52W5HJLIJLVKbBjg0tR+PuVtNj
|
||||||
|
IE1wk8/lam9ddrjoYtb8d6pZxAJCpfWDmCd+JmvMDrOQDbM4Wto+yetIFHJLtLseLUuOuN
|
||||||
|
m9VPNmIhfeRnV0H20LSjg21uGQ2yt08FmaZwS59pQR32Ace5BrMie2jYFj8eV/WuZh9HtL
|
||||||
|
oz/Oh3jJkuOOM5ciPopQtKYgQpm+hoV7IVLwnUC3R+HyRfYI0XMtG41LP01LMzKKK3UQFM
|
||||||
|
s1Lyf7fbCKLam0IEXO0ka9hG1MlkOQeX+gr5nw02GEZMu/3m4haKW8yUHT7eTairx+4v26
|
||||||
|
K31B156uRq3YHhr+tgAAAQEA9sb+rXa5JpMrlU+4EMx9J2p/1CUf7lEvQi3YBX46jawNE9
|
||||||
|
gebv3/VC7iiJcFYguWV6fFzp2LACu/jWIChBaG4g60FARNLjU4jNOrmz4J4zeE0XbUAmj3
|
||||||
|
aUx4mAgUL7Mj5z74bsrPnM5kTYYwqRO2dsMV6SZkkOcVio+MKl+c/jZBDxsqneE/mjj7+H
|
||||||
|
0735MiQ4FRAVOaPZRO3goAIiU1OyFcpTLfQfgrhqDupGpXxwMwxrs6VL0dfRiH1B6vwp7l
|
||||||
|
rEsUxyW7N+NCdZGXiMrdP5QtSPsplbfgyi2mDwNIkf6czzODAvNVnyrJINcIQIjsVCwozW
|
||||||
|
SySEjceVnNtr6evQAAAQEA1OSQ0QbeO1p6INtdxJ5gO2fRkIetO1jtFqfNydvWUku5LToX
|
||||||
|
UyImHL78L/qUfucwj+5I3qrnA5zMMTazUUco6oKP20hhqiFgaCsnI8Flhdcl1JE5GZhYmG
|
||||||
|
B6UB7zNgJDW4eC2k8tMfFA2ubDVlCo2m1r3bxzCJFmZVPp+tgG+IzTFKPAjIEMlaAzGzBL
|
||||||
|
xPQFJdUjloJ9aHkrBxrNAqzaAt0NYLNhX326gTy4KmhQKJ+whcavMbBojGThvWw05iG0cQ
|
||||||
|
3AWbEBWhdGM9RsRG9kvntAOYqK7oHOeT4DJ467LNiv3usxX2VXilHqKt+fbxoy1/Ha6oas
|
||||||
|
dBYjkVGhfCvr9QAAABZmcmVlZGFrQE1hYy1taW5pLmxvY2FsAQID
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDNOR+75yVy/k2dDi5vK7ZMYlo8a/z4j39Di3QTpTXAgZap5HY80bh49m9EdCWxFwGU03V1r/G8IuTBj2HHlJWeARJzRzjnVBavgqUqDNyPcvMheJ7U1hiwrG6NUu3Lmk4xJTYVaWuGVDyjtbyE5Domv6xvdxnt1Hx1pUaBVZxoi+NcOwxy4+nolE5wgADncdjF8mNjngxrDtZlLXi7IrHL1x1OsYiEnD6LrGX7+mxnWjRMcV7EyipsGwVggM1/FdoOC2tdUEiVIGWWw9lS/lDEZUfuiO5c8Xxq4Q2nhsane7U4+gmbh38fGy0FEK189wLFoodgsGHw6S36a8ivpQfkG8/3ayi47I47iprlBtnrwcJX0Jj9VrMWL0ed8tnnDnddqq1vRkGG2U+l8qjHs2lOHW48xkjWKfeL70bd2jPuoXc0PMlUMIji2lLCK7Eviwl9/x9+VyznwN4D2iWmd+mBL23hdS5kQYJIKFTQyZOzW3W9AdtltB3bCWh+9L354W+HFM6krf7SKFG9b5rETRR6zAnMSirSVk8BwYllBXmnfcRG/tWS0TvwFJfcNAkEEVqat9W+SOf3f9HK2twrMX3hd/iSFqd/GJI+4Spqns7z8yZoKhsM/airrXE/Ho8K4DvBxEgS43e2d3GnjehK+QmOjrpg9iqJSxwdzXFmFeRp4Q== freedak@Mac-mini.local
|
||||||
Reference in New Issue
Block a user