@2kog/pkg-editor 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # iruxu-editor
2
+
3
+ ## 组件文档
4
+ + [Article](./docs/article.md)
5
+ + [Tinymce](./docs/tinymce.md)
6
+ + [Upload](./docs/upload.md)
7
+
8
+ ## 开发
9
+ ```
10
+ $ npm install
11
+ $ npm run dev //本地调试
12
+ $ npm run serve //使用线上tinymce资源和接口
13
+ ```
14
+
15
+
16
+ ## Fork&构建流程
17
+ ### 构建发布
18
+ 本地执行`npm run build`,需要将文章与编辑器共用样式在tinymce进行生成静态css
19
+
20
+
21
+ ## Tinymce
22
+ ### 特性
23
+ + 基于tinymce v5.2.2扩展
24
+ + 保留v4版本分割线规则
25
+ + 内置powerpaste&checklist插件
26
+ + 增加插入B站视频插件
27
+ + 增加插入折叠文本插件
28
+ + 增加mathjax支持latex
29
+
30
+ ### 插件添加步骤
31
+ 1. tinymce/icons/custom/icons.js 添加svg图标,需设置尺寸,注意视口大小,移除换行符等
32
+ 2. tinymce/plugins目录,复制videox(input),foldtext(null)目录作为参考新建插件目录,替换videox为新插件名
33
+ 3. 编辑器配置中激活插件和添加工具栏项
34
+
35
+
@@ -0,0 +1,77 @@
1
+ module.exports = {
2
+
3
+ // 编辑器
4
+ /*------------------*/
5
+ plugins: [
6
+ "link autolink",
7
+ "hr lists advlist table codeinline codesample checklist foldtext latex",
8
+ "image emoticons media",
9
+ "code fullscreen wordcount powerpaste pagebreak printpage", // template anchor jx3icon autosave
10
+ ],
11
+ toolbar: [
12
+ "undo | formatselect | fontsizeselect | forecolor backcolor | bold italic underline strikethrough superscript subscript | link unlink | fullscreen code", //restoredraft
13
+ "removeformat | hr alignleft aligncenter alignright alignjustify indent outdent | bullist numlist checklist table blockquote foldtext codeinline codesample latex | image media", // template anchor jx3icon
14
+ ],
15
+ mobile: {
16
+ toolbar_drawer: true,
17
+ toolbar: [
18
+ "undo emoticons bold forecolor backcolor removeformat pagebreak fullscreen",
19
+ "hr alignleft aligncenter alignright alignjustify indent outdent bullist numlist checklist table blockquote codesample latex media",
20
+ ],
21
+ },
22
+ color_map: [
23
+ "FF99CC",
24
+ "浅粉",
25
+ "FF3399",
26
+ "深粉",
27
+ "FF0000",
28
+ "正红",
29
+ "CC99FF",
30
+ "紫色",
31
+ "9933ff",
32
+ "深紫",
33
+
34
+ "FFFF99",
35
+ "浅黄",
36
+ "FFFF00",
37
+ "金黄",
38
+ "FFCC00",
39
+ "亮黄",
40
+ "FFCC99",
41
+ "浅桃",
42
+ "FF6600",
43
+ "橘色",
44
+
45
+ "CCFFCC",
46
+ "浅绿",
47
+ "9bf915",
48
+ "荧光绿",
49
+ "00FF00",
50
+ "辣眼绿",
51
+ "49c10f",
52
+ "深绿",
53
+ "008080",
54
+ "深青",
55
+
56
+ "CCFFFF",
57
+ "浅蓝",
58
+ "00FFFF",
59
+ "参考线",
60
+ "00CCFF",
61
+ "天蓝",
62
+ "99CCFF",
63
+ "蔚蓝",
64
+ "0000FF",
65
+ "辣眼蓝",
66
+
67
+ "CC0000",
68
+ "深红",
69
+ "000000",
70
+ "黑色",
71
+ ],
72
+
73
+ // 上传组件
74
+ /*------------------*/
75
+ imgTypes: ["jpg", "png", "gif", "bmp", "webp", "jpeg", "heic", "heif", "avif", "tif", "tiff","svg"],
76
+ videoTypes: ["mp4", "mov", "avi", "flv", "3gp", "wmv", "mkv", "webm", "m4v"]
77
+ }
@@ -0,0 +1,5 @@
1
+ // 主题色(链接等)
2
+ @primary:#0366d6;
3
+
4
+ // 文字颜色(黑底、白底)
5
+ @color: #3d454d;
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@2kog/pkg-editor",
3
+ "version": "0.0.1",
4
+ "description": "Custom Tinymce、Article Renderer",
5
+ "scripts": {
6
+ "serve": "npm run dev",
7
+ "dev:vue": "vue-cli-service serve",
8
+ "dev:tinymce": "serve -l 5120 ./tinymce",
9
+ "dev": "npm run dev:vue & npm run dev:tinymce",
10
+ "build": "npx lessc -x ./tinymce/skins/content/default/content.less ./tinymce/skins/content/default/content.min.css",
11
+ "lint": "vue-cli-service lint"
12
+ },
13
+ "dependencies": {
14
+ "@element-plus/icons-vue": "^2.0.10",
15
+ "@tinymce/tinymce-vue": "^5.0.0",
16
+ "axios": "^1.3.2",
17
+ "cheerio": "^1.1.2",
18
+ "element-plus": "^2.2.29",
19
+ "highlight.js": "^11.7.0",
20
+ "jquery": "^3.5.1",
21
+ "katex": "^0.16.4",
22
+ "lodash": "^4.17.21",
23
+ "sanitize-html": "^2.17.0",
24
+ "uuid": "^9.0.1",
25
+ "viewerjs": "^1.11.3",
26
+ "vue": "^3.2.13"
27
+ },
28
+ "devDependencies": {
29
+ "@babel/core": "^7.12.16",
30
+ "@babel/eslint-parser": "^7.12.16",
31
+ "@typescript-eslint/eslint-plugin": "^5.31.0",
32
+ "@typescript-eslint/parser": "^5.31.0",
33
+ "@vue/cli-plugin-babel": "~5.0.0",
34
+ "@vue/cli-plugin-eslint": "~5.0.0",
35
+ "@vue/cli-plugin-typescript": "~5.0.0",
36
+ "@vue/cli-service": "~5.0.0",
37
+ "@vue/eslint-config-typescript": "^11.0.0",
38
+ "babel-loader": "^8.2.5",
39
+ "csslab": "^6.0.1",
40
+ "eslint": "^7.32.0",
41
+ "eslint-plugin-vue": "^9.3.0",
42
+ "husky": "^8.0.0",
43
+ "less": "^3.11.1",
44
+ "less-loader": "^11.0.0",
45
+ "lint-staged": "^13.0.2",
46
+ "prettier": "2.7.1",
47
+ "serve": "^14.2.0",
48
+ "style-resources-loader": "^1.5.0",
49
+ "typescript": "~4.5.5",
50
+ "unplugin-vue-define-options": "^1.2.1",
51
+ "url": "^0.11.0",
52
+ "vue-svg-inline-loader": "^2.1.3"
53
+ },
54
+ "browserslist": [
55
+ "> 1%",
56
+ "last 2 versions",
57
+ "not dead",
58
+ "not ie 11"
59
+ ],
60
+ "lint-staged": {
61
+ "**/*": "prettier --write --ignore-unknown"
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/2kog/pkg-editor.git"
66
+ }
67
+ }
@@ -0,0 +1,23 @@
1
+ // tinymce文章样式
2
+
3
+ @import "./var.less";
4
+
5
+ @import "tinymce/_.less";
6
+ @import "tinymce/a.less";
7
+ @import "tinymce/list.less";
8
+ @import "tinymce/quote.less";
9
+ @import "tinymce/hr.less";
10
+ @import "tinymce/table.less";
11
+ @import "tinymce/img.less";
12
+ @import "tinymce/p.less";
13
+ @import "tinymce/h.less";
14
+ @import "tinymce/code.less";
15
+ @import "tinymce/fold.less";
16
+ @import "tinymce/plugin.less";
17
+ @import "tinymce/imgpreview.less";
18
+ @import "tinymce/latex.less";
19
+ @import "tinymce/nextpage.less";
20
+
21
+ @import "tinymce/video.less";
22
+
23
+ @import "module/directory.less";
@@ -0,0 +1,104 @@
1
+ .c-article-directory {
2
+ h1,
3
+ h2,
4
+ h3,
5
+ h4,
6
+ h5,
7
+ h6 {
8
+ margin: 0;
9
+ padding: 0;
10
+ cursor: pointer;
11
+ font-weight: normal;
12
+ white-space: nowrap;
13
+ word-wrap: normal;
14
+ word-break: keep-all;
15
+ text-overflow: ellipsis;
16
+ overflow: hidden;
17
+ letter-spacing: 0.2px;
18
+ line-height: 20px;
19
+ padding: 8px 0;
20
+ &:hover {
21
+ // font-weight: 600;
22
+ color: @primary;
23
+ }
24
+ font-size: 12px;
25
+ }
26
+ a {
27
+ color: @color;
28
+ }
29
+
30
+ h1,
31
+ h2,
32
+ h3,
33
+ h4,
34
+ h5,
35
+ h6 {
36
+ &::before {
37
+ content: "\e78b";
38
+
39
+ font-family: element-icons !important;
40
+ speak: none;
41
+ font-style: normal;
42
+ font-weight: 400;
43
+ font-variant: normal;
44
+ text-transform: none;
45
+ line-height: 1;
46
+ vertical-align: baseline;
47
+ display: inline-block;
48
+ -webkit-font-smoothing: antialiased;
49
+ margin-right: 5px;
50
+ font-size: 16px;
51
+ }
52
+ }
53
+
54
+ .lv2 {
55
+ padding-left: 20px;
56
+ }
57
+
58
+ .lv3 {
59
+ padding-left: 40px;
60
+ }
61
+
62
+ .lv0 {
63
+ display: none;
64
+ }
65
+ }
66
+
67
+ .c-article-directory-title {
68
+ margin-bottom: 5px;
69
+ .c-article-directory-title-label {
70
+ font-weight: 300;
71
+ font-size: 18px;
72
+ .u-icon{
73
+ font-size: 20px;
74
+ }
75
+ }
76
+ .c-article-directory-title-skip,
77
+ .c-article-directory-title-folder {
78
+ font-size: 14px;
79
+ float: right;
80
+ padding: 0 5px;
81
+ line-height: 25px;
82
+ color: darken(#dcdfe6, 5%);
83
+ cursor: pointer;
84
+ &:hover {
85
+ color: darken(#dcdfe6, 20%);
86
+ }
87
+ }
88
+ }
89
+ .c-article-directory-content {
90
+ padding: 10px 15px;
91
+ }
92
+
93
+ @keyframes focusFade {
94
+ from {
95
+ background-color: #c3fcff;
96
+ }
97
+ to {
98
+ background-color: transparent;
99
+ }
100
+ }
101
+
102
+ .c-article .isScrollFocus {
103
+ animation: focusFade 0.5s ease-in-out;
104
+ }
@@ -0,0 +1,81 @@
1
+ // tinymce编辑器样式
2
+ @import 'var.less';
3
+ .c-editor-header {
4
+ .mb(10px);
5
+ .clearfix;
6
+
7
+ .c-upload,
8
+ .c-resource {
9
+ .fl;
10
+ }
11
+ .c-upload {
12
+ .mr(5px);
13
+ }
14
+ }
15
+
16
+ .c-editor-emotion {
17
+ max-height: 168px;
18
+ overflow: auto;
19
+
20
+ &::-webkit-scrollbar {
21
+ width: 4px;
22
+ }
23
+
24
+ &::-webkit-scrollbar-track,
25
+ &::-webkit-scrollbar-track-piece {
26
+ background-color: #fafafa;
27
+ border-radius: 6px;
28
+ }
29
+
30
+ &::-webkit-scrollbar-thumb {
31
+ background-color: #eee;
32
+ border-radius: 6px;
33
+ }
34
+
35
+ &::-webkit-scrollbar-button,
36
+ &::-webkit-scrollbar-corner,
37
+ &::-webkit-resizer {
38
+ display: none;
39
+ }
40
+ }
41
+
42
+ .c-editor-tinymce {
43
+ .u-tutorial {
44
+ padding: 0 10px;
45
+ .mt(10px);
46
+ .mb(10px);
47
+ .fz(13px,32px);
48
+ .el-alert__icon.is-big {
49
+ .fz(18px);
50
+ }
51
+ .el-alert__content {
52
+ padding-left: 0;
53
+ }
54
+ .el-alert__closebtn {
55
+ top: 12px;
56
+ }
57
+ a {
58
+ color: @primary;
59
+ }
60
+ a:hover {
61
+ box-shadow: 0 1px 0 @primary;
62
+ }
63
+ .el-alert__description {
64
+ margin: 0 !important;
65
+ }
66
+ }
67
+
68
+ .t-emotion {
69
+ padding: unset;
70
+ border: unset;
71
+ margin: unset;
72
+ }
73
+ }
74
+
75
+ @media screen and (max-width: @phone) {
76
+ .c-editor-tinymce {
77
+ .u-tutorial {
78
+ .none;
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,119 @@
1
+ @import "var.less";
2
+ .c-upload {
3
+ .c-upload-trigger {
4
+ margin-right: 5px;
5
+ }
6
+
7
+ .c-upload-toolbar {
8
+ .flex(y);
9
+ margin-bottom: 20px;
10
+ .u-upload-clear {
11
+ .el-icon{
12
+ margin-right: 3px;
13
+ }
14
+ margin-right: 5px;
15
+ }
16
+ .u-upload-tip {
17
+ padding-right: 10px;
18
+ padding-top: 6px;
19
+ padding-bottom: 6px;
20
+ .fz(12px, 16px);
21
+ padding-right: 20px;
22
+ .db;
23
+ .nobreak;
24
+ }
25
+ }
26
+
27
+ .el-dialog__body {
28
+ padding-top: 0;
29
+ }
30
+
31
+ .el-upload-list li {
32
+ outline: none;
33
+ }
34
+
35
+ .el-upload-list__item {
36
+ &:hover {
37
+ border: 1px solid #13ce66;
38
+ }
39
+ }
40
+
41
+ .el-upload--picture-card:hover{
42
+ background-color:#fafcff;
43
+ }
44
+
45
+ // 列表
46
+ .u-file-wrapper {
47
+ .size(100%);
48
+ &.isSelected {
49
+ }
50
+ &.disabled {
51
+ cursor: default;
52
+ opacity: 0.38;
53
+ border-color: #eee;
54
+ .u-fileplaceholder {
55
+ fill: #aaa;
56
+ }
57
+ }
58
+ .pointer;
59
+ }
60
+ .u-filebox {
61
+ .x;
62
+ padding: 37px;
63
+ }
64
+ .u-fileplaceholder {
65
+ width: 40px;
66
+ height: 40px;
67
+ fill: @primary;
68
+ }
69
+ .u-filename {
70
+ .db;
71
+ .nobreak;
72
+ }
73
+
74
+ // 勾选
75
+ .u-file-select-label {
76
+ position: absolute;
77
+ right: -15px;
78
+ top: -6px;
79
+ width: 40px;
80
+ height: 24px;
81
+ background: #13ce66;
82
+ text-align: center;
83
+ -webkit-transform: rotate(45deg);
84
+ transform: rotate(45deg);
85
+ -webkit-box-shadow: 0 0 1pc 1px rgba(0, 0, 0, 0.2);
86
+ box-shadow: 0 0 1pc 1px rgba(0, 0, 0, 0.2);
87
+
88
+ i {
89
+ font-size: 12px;
90
+ margin-top: 12px;
91
+ transform: rotate(-45deg);
92
+ }
93
+ }
94
+
95
+ .c-large-dialog {
96
+ &.el-dialog {
97
+ .w(60%);
98
+ height: 70%;
99
+ .el-dialog__body {
100
+ height: calc(100% - 100px);
101
+ overflow-y: auto;
102
+ box-sizing: border-box;
103
+ }
104
+ }
105
+ }
106
+ @media screen and (max-width: @notebook) {
107
+ .c-large-dialog .el-dialog {
108
+ .w(90%);
109
+ }
110
+ }
111
+ @media screen and (max-width: @ipad-y) {
112
+ .c-large-dialog .el-dialog {
113
+ margin: 0 !important;
114
+ .size(100%);
115
+ max-height: none;
116
+ .h(100%);
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,17 @@
1
+ @import "../../../config/global.less";
2
+
3
+ // 颜色
4
+ @bg-black: #24292e;
5
+ @bg-gray: #f6f8fa;
6
+ @bg-light: #fafbfc;
7
+ @border-hr: #e1e4e8;
8
+ @border: #e4e7ed;
9
+
10
+ // 适配
11
+ @smallpc: 1680px; //21寸显示器
12
+ @notebook: 1440px; //笔记本电脑
13
+ @mininote: 1280px; //小屏笔记本
14
+ @ipad: 1133px; //平板,适配到8.3mini6
15
+ @ipad-y: 1023px; //竖屏
16
+ @phone: 720px; //手机
17
+ @ip5: 374px; //iphone5超小屏
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 469.333 469.333" style="enable-background:new 0 0 469.333 469.333;" xml:space="preserve">
5
+ <g>
6
+ <g>
7
+ <g>
8
+ <path d="M466.208,88.458L380.875,3.125c-2-2-4.708-3.125-7.542-3.125H42.667C19.146,0,0,19.135,0,42.667v384
9
+ c0,23.531,19.146,42.667,42.667,42.667h384c23.521,0,42.667-19.135,42.667-42.667V96
10
+ C469.333,93.167,468.208,90.458,466.208,88.458z M106.667,21.333h234.667v128c0,11.76-9.563,21.333-21.333,21.333H128
11
+ c-11.771,0-21.333-9.573-21.333-21.333V21.333z M384,448H85.333V256H384V448z M448,426.667c0,11.76-9.563,21.333-21.333,21.333
12
+ h-21.333V245.333c0-5.896-4.771-10.667-10.667-10.667h-320c-5.896,0-10.667,4.771-10.667,10.667V448H42.667
13
+ c-11.771,0-21.333-9.573-21.333-21.333v-384c0-11.76,9.563-21.333,21.333-21.333h42.667v128C85.333,172.865,104.479,192,128,192
14
+ h192c23.521,0,42.667-19.135,42.667-42.667v-128h6.25L448,100.417V426.667z"/>
15
+ <path d="M266.667,149.333h42.667c5.896,0,10.667-4.771,10.667-10.667V53.333c0-5.896-4.771-10.667-10.667-10.667h-42.667
16
+ c-5.896,0-10.667,4.771-10.667,10.667v85.333C256,144.562,260.771,149.333,266.667,149.333z M277.333,64h21.333v64h-21.333V64z"
17
+ />
18
+ </g>
19
+ </g>
20
+ </g>
21
+ </svg>
@@ -0,0 +1,141 @@
1
+ // formatLink.js
2
+ import { load } from "cheerio";
3
+
4
+ /** 规范化 whitelist:统一小写、去空格、允许传入带协议的域名 */
5
+ function normalizeWhitelist(list = []) {
6
+ return (Array.isArray(list) ? list : [])
7
+ .map((x) => String(x || "").trim().toLowerCase())
8
+ .filter(Boolean)
9
+ .map((x) => {
10
+ // 允许用户写 https://xxx.com
11
+ try {
12
+ if (/^https?:\/\//i.test(x)) return new URL(x).hostname.toLowerCase();
13
+ } catch {
14
+ console.warn(`formatLink: 无法解析 whitelist 域名 "${x}",已忽略该项`);
15
+ }
16
+ return x;
17
+ });
18
+ }
19
+
20
+ /** 是否相对链接(默认认为站内,视为白名单) */
21
+ function isRelativeHref(href = "") {
22
+ const s = href.trim();
23
+ if (!s) return true;
24
+ if (s.startsWith("#") || s.startsWith("?") || s.startsWith("/")) return true;
25
+ if (s.startsWith("./") || s.startsWith("../")) return true;
26
+ // 没有 scheme 且不是 //xxx,通常是相对路径(如 a/b)
27
+ return !/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(s) && !s.startsWith("//");
28
+ }
29
+
30
+ /** 从 href 里提取 hostname(只处理 http/https/协议相对 //) */
31
+ function getHostnameFromHref(href = "") {
32
+ const s = href.trim();
33
+ if (!s) return "";
34
+
35
+ if (s.startsWith("//")) {
36
+ try {
37
+ return new URL("https:" + s).hostname.toLowerCase();
38
+ } catch {
39
+ return "";
40
+ }
41
+ }
42
+
43
+ if (/^https?:\/\//i.test(s)) {
44
+ try {
45
+ return new URL(s).hostname.toLowerCase();
46
+ } catch {
47
+ return "";
48
+ }
49
+ }
50
+
51
+ return "";
52
+ }
53
+
54
+ /**
55
+ * whitelist 规则:
56
+ * - "xxx.com" 只匹配根域
57
+ * - "*.xxx.com" 只匹配子域(a.xxx.com),不匹配根域(xxx.com)
58
+ */
59
+ function matchHost(hostname, whitelist) {
60
+ const host = (hostname || "").toLowerCase();
61
+ if (!host) return false;
62
+
63
+ for (const rule of whitelist) {
64
+ if (!rule) continue;
65
+
66
+ if (rule.startsWith("*.")) {
67
+ const root = rule.slice(2);
68
+ if (root && host.endsWith("." + root)) return true;
69
+ continue;
70
+ }
71
+
72
+ if (host === rule) return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ /**
78
+ * @param {string} html - HTML 字符串(可为片段)
79
+ * @param {string[]} whitelist - 例如 ["xxx.com", "*.xxx.com"]
80
+ * @param {boolean} strict - true 时,非白名单链接 href 强制置空
81
+ */
82
+ export default function formatLink(html, whitelist = [], strict = false) {
83
+ if (!html) return html;
84
+
85
+ const wl = normalizeWhitelist(whitelist);
86
+ const hasWhitelist = wl.length > 0;
87
+
88
+ // cheerio 用 root 包一下,保证片段也能正常选取
89
+ const $ = load(`<root>${html}</root>`, { decodeEntities: false });
90
+
91
+ $("a[href]").each((_, el) => {
92
+ const a = $(el);
93
+ const hrefRaw = a.attr("href") ?? "";
94
+ const href = String(hrefRaw).trim();
95
+ if (!href) return;
96
+
97
+ // markdown 锚点:强制 _self
98
+ if (href.startsWith("#")) {
99
+ a.attr("target", "_self");
100
+ return;
101
+ }
102
+
103
+ // 安全兜底:javascript: 一律干掉
104
+ if (/^javascript:/i.test(href)) {
105
+ a.attr("href", "");
106
+ return;
107
+ }
108
+
109
+ // mailto/tel 等不参与白名单判断(你也可以按 strict 强行置空,这里先按常见做法放行)
110
+ if (/^(mailto:|tel:|sms:)/i.test(href)) {
111
+ return;
112
+ }
113
+
114
+ // 相对链接默认视为站内
115
+ let isWhite = true;
116
+
117
+ if (!isRelativeHref(href)) {
118
+ const host = getHostnameFromHref(href);
119
+ isWhite = matchHost(host, wl);
120
+ } else {
121
+ isWhite = true;
122
+ }
123
+
124
+ // 只有设置了 whitelist 才触发“非白名单处理”
125
+ if (hasWhitelist && !isWhite) {
126
+ if (strict) {
127
+ a.attr("href", "");
128
+ return;
129
+ }
130
+
131
+ // 如果本身就是 _blank,就不管;否则强制 _blank
132
+ const target = String(a.attr("target") || "").toLowerCase();
133
+ if (target !== "_blank") {
134
+ a.attr("target", "_blank");
135
+ }
136
+ }
137
+ });
138
+
139
+ // 取出 root 里原始内容
140
+ return $("root").html() || "";
141
+ }