# aardio 范例: WebView2 - Readability 网页阅读模式提取正文、清洗、转为 Markdown

``````aardio
import win.ui;
/*DSG{{*/
var winform = win.form(text="WebView2 - Readability 网页阅读模式提取正文、清洗、转为 Markdown";right=966;bottom=622)
winform.add(
btnExtract={cls="button";text="提取正文 / 复制 Markdown";left=758;top=6;right=950;bottom=36;dr=1;dt=1;z=3};
btnGo={cls="button";text="加载网址";left=648;top=6;right=748;bottom=36;dr=1;dt=1;z=2};
custom={cls="custom";left=0;top=45;right=966;bottom=622;db=1;dl=1;dr=1;dt=1;z=4};
editUrl={cls="edit";left=12;top=10;right=641;bottom=34;dl=1;dr=1;dt=1;edge=1;multiline=1;z=1}
)
/*}}*/

import web.view;

// 使用 custom 控件作为宿主窗口（方便我们在顶部保留输入框和按钮）
var wb = web.view(winform.custom);

/*
通过 wb.export 将 aardio 函数导出为 JavaScript 全局函数。
提取后的 JS 对象会自动转换为 aardio 中的表（table）。
*/
wb.export({
	saveCleanData = function(article){
		if( !article || !article.content ){
			winform.msgbox("提取失败，此页面可能没有标准的文章正文格式。");
			return true;
		}
		
		// 容错处理，防止对应字段为 null
		var title = article.title : "未知标题";
		var byline = article.byline : ""; 
		
		import web.turndown;

		var turndownService = web.turndown( codeBlockStyle = "fenced" );
		turndownService.remove('script');
		turndownService.remove('style');
		
		//启用 gfm（GitHub Flavored Markdown）插件。
		turndownService.useGfm()
		
		// 添加自定义规则处理带类名的代码块
		turndownService.addRule('codeBlock', "{
    		filter: function(node) {
        		return node.nodeName === 'PRE' && node.classList.contains('code');
    		},
    		replacement: function(content, node) {
        		var language = node.classList.item(1)  || ''; 
        		language = language.replace(/^language-/, '');
        		return '```' + language + '\n' + content.trim() + '\n```';
    		}
		}");
		
		
		//用法参考: https://github.com/mixmark-io/turndown
		var md = turndownService.turndown( 
			article.content
		);
		
		import string.markdown;
		var content = string.markdown().render(md);
		
		import win.clip;
		win.clip.write(md)

		
		// 构造干净的阅读模式 HTML（加入一些优美的排版 CSS）
		var html = `
		<!DOCTYPE html>
		<html>
		<head>
			<meta charset="utf-8">
			<title>` + title + `</title>
			<style>
				body {
					font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
					line-height: 1.8;
					color: #333;
					max-width: 800px;
					margin: 0 auto;
					padding: 20px;
					background: #f4f5f7;
				}
				article {
					background: #fff;
					padding: 40px;
					border-radius: 8px;
					box-shadow: 0 4px 12px rgba(0,0,0,0.05);
				}
				h1 { text-align: center; color: #222; font-size: 2em; margin-bottom: 0.5em; }
				.byline { text-align: center; color: #888; font-size: 0.9em; margin-bottom: 30px; }
				img { max-width: 100%; height: auto; display: block; margin: 20px auto; border-radius: 4px; }
				p { margin-bottom: 1.2em; font-size: 1.1em; }
				a { color: #1a73e8; text-decoration: none; }
				a:hover { text-decoration: underline; }
				pre, code { background: #f1f3f5; padding: 4px 8px; border-radius: 4px; font-family: Consolas, monospace; }
				pre { padding: 15px; overflow-x: auto; }
			</style>
		</head>
		<body>
			<article>
				<h1>` + title + `</h1>
				<div class="byline">` + byline + `</div>
				` + content + `
			</article>
		</body>
		</html>
		`;
		
		/*
		被 JS 回调的 aardio 函数中应避免进行阻塞调用。
		这里使用 setTimeout 异步更新 WebView 内容。
		*/
		winform.setTimeout(
			function(){
				wb.html = html;
			}
		);
		
		return true; // 务必返回非 null 值
	}
})

// 加载网址事件
winform.btnGo.oncommand = function(id,event){
	var url = winform.editUrl.text;
	if(#url) wb.go(url); 
}

// 提取正文事件
winform.btnExtract.oncommand = function(id,event){
	winform.btnExtract.disabled = true;
	
	import inet.http;
	
	// 1. 获取 Readability.js 的代码文本，注意 inet.http.get 是创建新线程下载
	var readabilityJs = inet.http.get("https://lib.baomitu.com/readability/0.6.0/Readability.min.js");
	
	if(!readabilityJs){
		winform.msgbox("无法下载 Readability.js 库，请检查网络！");
		winform.btnExtract.disabled = false;
		return;
	}
	
	// 2. 拼接我们自己的提取执行代码
	var script = readabilityJs + `
	;(() => {
		try {
			// Readability 库在解析时会破坏原有的 DOM 结构，因此必须传入 document 的深度克隆
			let documentClone = document.cloneNode(true);
			let reader = new Readability(documentClone);
			let article = reader.parse();
			
			// 调用 aardio 导出的 saveCleanData 函数，把提取到的 JSON 抛给 aardio
			window.saveCleanData(article);
		} catch(e) {
			window.saveCleanData(null);
		}
	})();
	`;
	
	//这比在页面内创建 <script src="..."> 标签更稳定，无视目标网站的 CSP 拦截。
	wb.doScript(script);
	
	winform.btnExtract.disabled = false;  
}

// 监听导航事件，用户在网页内点击跳转时自动同步网址栏
wb.onDocumentInit = function(url) {
	winform.editUrl.text = url;
}

// 启动时自动加载预设的默认网址
winform.btnGo.oncommand();

winform.show();
win.loopMessage();
``````