Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
"dev": "pnpm -C apps/extension dev",
"dev:chrome": "pnpm -C apps/extension dev:chrome",
"dev:watch": "pnpm -C apps/extension dev:watch",
"lint": "pnpm -r lint"
"lint": "pnpm -r lint",
"test": "node --test"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
}
71 changes: 45 additions & 26 deletions packages/core/src/platforms/wechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,58 @@ const WechatPlatform = {
type: 'wechat',
}

function getEditorArea(editor) {
return (editor?.clientHeight || 0) * (editor?.clientWidth || 0)
}

function isWechatTitleEditor(editor, titleEditor) {
return Boolean(editor)
&& (editor === titleEditor || Boolean(editor.closest?.('.title-editor__input')))
}

function pickWechatBodyProseMirrorCandidate(nodes, { titleInput, titleEditor } = {}) {
const bodyCandidates = nodes.filter(editor => !isWechatTitleEditor(editor, titleEditor))
if (bodyCandidates.length === 0)
return null
if (bodyCandidates.length === 1)
return bodyCandidates[0]

const byPlaceholder = bodyCandidates.find(editor =>
(editor.textContent || '').includes('从这里开始写正文'),
)
if (byPlaceholder)
return byPlaceholder

if (titleInput) {
const band = titleInput.getBoundingClientRect()
const belowTitle = bodyCandidates.filter((editor) => {
const rect = editor.getBoundingClientRect()
return rect.top >= band.bottom - 8
})
if (belowTitle.length > 0) {
return belowTitle.sort((a, b) => getEditorArea(b) - getEditorArea(a))[0]
}
}

return bodyCandidates.sort((a, b) => getEditorArea(b) - getEditorArea(a))[0]
}

// 微信公众号内容填充函数(在页面主世界中执行)
// 注意:需要先调用 injectUtils 注入 window.waitFor
async function fillWechatContent(title, htmlBody) {
/**
* 后台改版后可能存在多个 `.ProseMirror`(标题区也可能是 ProseMirror),
* `querySelector('.ProseMirror')` 常会命中标题编辑器,导致正文 HTML 被贴进标题。
* 另外,正文编辑器有时会比标题编辑器晚挂载,这时也要继续等待,不能把唯一节点误判成正文。
*/
function pickWechatBodyProseMirror() {
const nodes = [...document.querySelectorAll('.ProseMirror')]
if (nodes.length === 0)
return null
if (nodes.length === 1)
return nodes[0]

const byPlaceholder = nodes.find(el =>
(el.textContent || '').includes('从这里开始写正文'),
)
if (byPlaceholder)
return byPlaceholder

const titleInput = document.querySelector('#title')
if (titleInput) {
const band = titleInput.getBoundingClientRect()
const belowTitle = nodes.filter((el) => {
const r = el.getBoundingClientRect()
return r.top >= band.bottom - 8
})
if (belowTitle.length > 0) {
return belowTitle.sort(
(a, b) => (b.clientHeight * b.clientWidth) - (a.clientHeight * a.clientWidth),
)[0]
}
}

return nodes.sort(
(a, b) => (b.clientHeight * b.clientWidth) - (a.clientHeight * a.clientWidth),
)[0]
const titleEditor = document.querySelector('.title-editor__input .ProseMirror')
return pickWechatBodyProseMirrorCandidate(nodes, { titleInput, titleEditor })
}

async function waitForBodyEditor(timeout = 15000) {
Expand Down Expand Up @@ -351,5 +366,9 @@ async function syncWechatContent(tab, content, helpers) {
}

// 导出
export { WechatPlatform, fillWechatContent, syncWechatContent }

export {
WechatPlatform,
fillWechatContent,
pickWechatBodyProseMirrorCandidate,
syncWechatContent,
}
90 changes: 90 additions & 0 deletions tests/wechat.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import assert from 'node:assert/strict'
import test from 'node:test'

import { pickWechatBodyProseMirrorCandidate } from '../packages/core/src/platforms/wechat.js'

function createEditor({
textContent = '',
top = 0,
height = 120,
width = 640,
classes = [],
} = {}) {
const classSet = new Set(classes)
const rect = {
top,
bottom: top + height,
left: 0,
right: width,
width,
height,
}

return {
textContent,
clientHeight: height,
clientWidth: width,
getBoundingClientRect: () => rect,
closest: (selector) => {
const selectors = selector.split(',').map(item => item.trim().replace(/^\./, ''))
return selectors.some(item => classSet.has(item)) ? { selector } : null
},
}
}

function createTitleInput({ top = 0, height = 48 } = {}) {
return {
getBoundingClientRect: () => ({
top,
bottom: top + height,
left: 0,
right: 640,
width: 640,
height,
}),
}
}

test('只有标题编辑器时返回 null,避免把正文贴到标题', () => {
const titleInput = createTitleInput()
const titleEditor = createEditor({
top: 8,
height: 40,
width: 640,
classes: ['title-editor__input'],
})

const picked = pickWechatBodyProseMirrorCandidate([titleEditor], { titleInput, titleEditor })
assert.equal(picked, null)
})

test('标题和正文同时存在时优先选择正文编辑器', () => {
const titleInput = createTitleInput()
const titleEditor = createEditor({
top: 8,
height: 40,
width: 640,
classes: ['title-editor__input'],
})
const bodyEditor = createEditor({
textContent: '从这里开始写正文',
top: 180,
height: 520,
width: 720,
})

const picked = pickWechatBodyProseMirrorCandidate([titleEditor, bodyEditor], { titleInput, titleEditor })
assert.equal(picked, bodyEditor)
})

test('没有标题编辑器时保留单编辑器场景的兼容性', () => {
const bodyEditor = createEditor({
textContent: '正文内容',
top: 120,
height: 480,
width: 720,
})

const picked = pickWechatBodyProseMirrorCandidate([bodyEditor], {})
assert.equal(picked, bodyEditor)
})