fix(extension): remove broken Substack image link wrappers
This commit is contained in:
1 parent
fdad077843
commit
156cd5c574
7 files changed
+35
-2
No files matched your search
@@ -290,6 +290,20 @@ test('article metadata retains source date precision and normalized own headline
|
||||
const d=fixture('substack-article'),s=d.querySelector('script'),v=JSON.parse(s.textContent);v.headline='The Dot and the Swarm';d.querySelector('h1').textContent=v.headline;s.textContent=JSON.stringify(v);
|
||||
expect(substackContent(d,'https://www.oneusefulthing.org/p/the-dot-and-the-swarm').author).toBe('Ethan Mollick');
|
||||
});
|
||||
test('Substack image cleanup preserves captions and ordinary or labelled links without changing the source',async()=>{
|
||||
const {substackContent}=await import('./src/substack-content.ts'),{platformHtmlMarkdown}=await import('./src/platform-clip.ts');
|
||||
const d=fixture('substack-article'),body=d.querySelector('.body.markup'),url='https://www.oneusefulthing.org/p/the-dot-and-the-swarm';
|
||||
body.innerHTML='<p>Before <a href="https://example.test/source">source</a></p><figure><a class="image-link image2" href="https://example.test/full.png"><div><picture><img src="https://example.test/preview.png" alt="Chart"></picture></div></a><figcaption>Chart caption</figcaption></figure><p><a class="image-link" href="https://example.test/book"><img src="https://example.test/book.png">Read the book</a></p><p><a class="image-link" href="https://example.test/more">More reading</a></p><p><a href="https://example.test/other"><img src="https://example.test/other.png" alt="Other"></a></p><p>After</p>';
|
||||
const before=d.documentElement.outerHTML,article=substackContent(d,url),markdown=platformHtmlMarkdown(article.body,url),nodes=[];
|
||||
const walk=node=>{nodes.push(node);(node.content || []).forEach(walk);};walk(markdownToDoc(markdown));
|
||||
expect(nodes.filter(node=>node.type==='image').map(node=>node.attrs.src)).toEqual(['https://example.test/preview.png','https://example.test/book.png','https://example.test/other.png']);
|
||||
const text=nodes.filter(node=>node.type==='text').map(node=>node.text).join('');
|
||||
expect(text).toContain('Before sourceChart captionRead the bookMore reading');expect(text).toContain('After');expect(text).not.toContain('full.png');
|
||||
for(const [label,href] of [['source','source'],['Read the book','book'],['More reading','more']]) expect(nodes.some(node=>node.text===label && node.marks?.some(mark=>mark.type==='link' && mark.attrs.href===`https://example.test/${href}`))).toBe(true);
|
||||
expect(markdown).toContain('[](https://example.test/other)');
|
||||
expect(d.documentElement.outerHTML).toBe(before);
|
||||
});
|
||||
|
||||
test('only a visible real Substack paywall blocks whole-article extraction',async()=>{
|
||||
const {substackContent}=await import('./src/substack-content.ts'),{substackPaywall}=await import('./src/article-platform.ts'),d=fixture('substack-paywall'),url='https://www.lennysnewsletter.com/p/how-to-build-ai-product-sense';
|
||||
expect(substackPaywall(d)).toBe(true);expect(substackContent(d,url)).toBeNull();d.querySelector('.paywall').style.display='none';expect(substackPaywall(d)).toBe(false);expect(substackContent(d,url)).not.toBeNull();
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createWindow, createDocument } from "@mixmark-io/domino";
|
||||
import { bundleTestScripts } from "./bundle-test-scripts.mjs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { markdownToDoc } from "../../packages/shared/src/content.ts";
|
||||
import { pageImageRefs, replacePageImageUrls } from "./src/page-images.ts";
|
||||
|
||||
test('whole-page and selection captures share table conversion and preserve code blank lines',async()=>{
|
||||
const [source]=bundleTestScripts(['capture']);
|
||||
@@ -91,6 +92,13 @@ test('Substack article capture retains sections and images on custom domains wit
|
||||
const unrelated=createDocument(html),schema=unrelated.querySelector('script'),data=JSON.parse(schema.textContent);data.url='https://www.oneusefulthing.org/p/other';schema.textContent=JSON.stringify(data);expect(capture(unrelated.documentElement.outerHTML,url).author).toBeUndefined();
|
||||
for(const content of ['Benefitting from the Bitter Lesson','Nope.','# Dots and Muse','# Swarms','# A Not-Entirely-Bitter Lesson','very cool pre-order bonus'])expect(page.markdown).toContain(content);
|
||||
expect(page.markdown.match(/!\[/g)).toHaveLength(3);
|
||||
const uploaded=pageImageRefs(page.markdown,url).map((image,index)=>({...image,resourceId:`substack-${index}`}));
|
||||
const stored=markdownToDoc(replacePageImageUrls(page.markdown,uploaded)),nodes=[];
|
||||
const walk=node=>{nodes.push(node);(node.content || []).forEach(walk);};walk(stored);
|
||||
expect(nodes.filter(node=>node.type==='image').map(node=>node.attrs.src)).toEqual(uploaded.map(image=>`/api/v1/resources/${image.resourceId}/blob`));
|
||||
const text=nodes.filter(node=>node.type==='text').map(node=>node.text).join('');
|
||||
expect(text).not.toContain('substackcdn.com/image/');
|
||||
expect(nodes.some(node=>node.type==='text' && node.text==='[')).toBe(false);
|
||||
for(const noise of ['Subscribe','Share','Comments and recommendations','One Useful Thing'])expect(page.markdown).not.toContain(noise);
|
||||
expect(capture(html,url,true).markdown).toBe('Selected paragraph');
|
||||
expect(capture(html,'https://oneusefulthing.substack.com/p/the-dot-and-the-swarm').markdown).toBe(page.markdown);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@edgeever/extension",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -12,6 +12,13 @@ export const substackContent = (doc: Document, pageUrl: string): ArticleContent
|
||||
if (!title?.textContent?.trim() || !content || content.closest('[hidden], [aria-hidden="true"]')) return null;
|
||||
const body = content.cloneNode(true) as Element;
|
||||
body.querySelectorAll('.subscribe-widget, .button-wrapper, .header-anchor-parent, .image-link-expand, script, style, svg, button, form, input, iframe, video, audio, source, [hidden], [aria-hidden="true"]').forEach((node)=>node.remove());
|
||||
// Substack wraps zoomable images in links containing block elements. Those
|
||||
// blocks split the Markdown link across blank lines, leaving visible syntax.
|
||||
for (const link of Array.from(body.querySelectorAll('a.image-link'))) {
|
||||
if (!link.querySelector('img') || platformText(link)) continue;
|
||||
while (link.firstChild) link.parentNode!.insertBefore(link.firstChild, link);
|
||||
link.remove();
|
||||
}
|
||||
const subtitle = article?.querySelector('.subtitle');
|
||||
if (subtitle?.textContent?.trim() && !subtitle.closest('[hidden], [aria-hidden="true"]')) {
|
||||
const paragraph = doc.createElement('p');
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
},
|
||||
"apps/extension": {
|
||||
"name": "@edgeever/extension",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"dependencies": {
|
||||
"@joplin/turndown-plugin-gfm": "1.0.68",
|
||||
"@mixmark-io/domino": "2.2.0",
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
Updated: 2026-10-18. Tracks [#588](https://github.com/tianma-if/edgeever/issues/588). This is an in-progress record, not a claim of complete cross-browser acceptance.
|
||||
|
||||
2026-10-19 addendum: Store-installed testing for [#587](https://github.com/tianma-if/edgeever/issues/587) found stray brackets and original image URLs around Substack images. The local and store capture scripts are identical, and the existing article fixture reproduces the problem; earlier text and image readback did not check the surrounding formatting. `0.2.1` removes only Substack's textless image-viewer link wrappers, retaining images, captions, and prose links, with regression assertions on the archived document. This does not claim store publication of the fix or expand historical acceptance for other platforms.
|
||||
|
||||
## Environment and baseline
|
||||
|
||||
- Code: upstream `67f627cc3c702c9cf2fa3fab9fabc67c70206888`; local `main` sync commit `7bd005ec`, with the instance-permission, SPA-message, Facebook alias/UI-text, current Jike Web detail, Android initialization and cross-document cancellation fixes applied on top.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
更新:2026-10-18。关联 [#588](https://github.com/tianma-if/edgeever/issues/588)。本文件是进行中的验收记录,不能用于宣称跨浏览器全部通过。
|
||||
|
||||
2026-10-19 补充:[#587](https://github.com/tianma-if/edgeever/issues/587) 的商店版验收发现,Substack 图片周围会保存多余的括号和原图链接。本地包与商店包的采集脚本一致,既有文章夹具也能复现;此前正文和图片回读通过,没有覆盖图片周围的排版。`0.2.1` 仅移除 Substack 无文字的图片查看链接包装,保留图片、图注和正文链接,并补充归档后文档的回归断言。此说明不代表修复已上架商店,也不扩大其他平台的历史验收范围。
|
||||
|
||||
## 环境与基线
|
||||
|
||||
- 代码:上游 `67f627cc3c702c9cf2fa3fab9fabc67c70206888`;本地 `main` 同步提交 `7bd005ec`,在此基线上新增实例权限、SPA 消息判断、Facebook 别名/界面文字、新版即刻 Web 详情、Android 初始化和展开中跨文档取消修复。
|
||||
|
||||
Reference in new issue
Block a user