@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 +35 -0
- package/config/global.js +77 -0
- package/config/global.less +5 -0
- package/package.json +67 -0
- package/src/assets/css/article.less +23 -0
- package/src/assets/css/module/directory.less +104 -0
- package/src/assets/css/tinymce.less +81 -0
- package/src/assets/css/upload.less +119 -0
- package/src/assets/css/var.less +17 -0
- package/src/assets/img/file.svg +21 -0
- package/src/assets/js/a.js +141 -0
- package/src/assets/js/code.js +13 -0
- package/src/assets/js/directory.js +113 -0
- package/src/assets/js/fold.js +11 -0
- package/src/assets/js/gallery.js +14 -0
- package/src/assets/js/hljs_languages.js +177 -0
- package/src/assets/js/iframe.js +112 -0
- package/src/assets/js/img.js +67 -0
- package/src/assets/js/katex.js +192 -0
- package/src/assets/js/nextpage.js +4 -0
- package/src/assets/js/pswp.js +67 -0
- package/src/assets/js/pswp_template.js +66 -0
- package/src/assets/js/renderImgPreview.js +18 -0
- package/src/assets/js/tex-mml-chtml.js +36310 -0
- package/src/assets/js/xss.js +72 -0
- package/src/components/Article.vue +218 -0
- package/src/components/Tinymce.vue +189 -0
- package/src/components/Upload.vue +288 -0
- package/src/pages/article.js +12 -0
- package/src/pages/article.vue +72 -0
- package/src/pages/index.js +5 -0
- package/src/pages/index.vue +8 -0
- package/src/pages/tinymce.js +14 -0
- package/src/pages/tinymce.vue +46 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import $ from "jquery";
|
|
2
|
+
const count = {
|
|
3
|
+
H1: 0,
|
|
4
|
+
H2: 0,
|
|
5
|
+
H3: 0,
|
|
6
|
+
H4: 0,
|
|
7
|
+
H5: 0,
|
|
8
|
+
H6: 0,
|
|
9
|
+
};
|
|
10
|
+
const MAX_DEPTH = 3;
|
|
11
|
+
const PREFIX_CLS = "lv"; //class前缀
|
|
12
|
+
|
|
13
|
+
function directory(from, to = "#directory") {
|
|
14
|
+
// 装载容器设置
|
|
15
|
+
const $box = $(to);
|
|
16
|
+
if (!to && $box.length) return;
|
|
17
|
+
|
|
18
|
+
// 遍历节点
|
|
19
|
+
let directories = $(from).find("h1,h2,h3,h4,h5,h6");
|
|
20
|
+
|
|
21
|
+
// 存在目录
|
|
22
|
+
if (directories.length > 1) {
|
|
23
|
+
let __markTags = analystics(from);
|
|
24
|
+
|
|
25
|
+
$box.html(
|
|
26
|
+
`<div class="c-article-directory" id="c-article-directory">
|
|
27
|
+
<div class="c-article-directory-title" id="c-article-directory-title">
|
|
28
|
+
<span class="c-article-directory-title-label" class="c-go-top"><i class="el-icon-s-promotion"></i> 导读</span>
|
|
29
|
+
<span class="c-article-directory-title-folder" class="c-go-top">↕ 折叠</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="c-article-directory-content" id="c-article-directory-content"></div>
|
|
32
|
+
</div>`
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const $directory = $("#c-article-directory-content");
|
|
36
|
+
const $skip = $(".c-article-directory-title-skip");
|
|
37
|
+
const $folder = $(".c-article-directory-title-folder");
|
|
38
|
+
|
|
39
|
+
// 顶部按钮
|
|
40
|
+
$folder.on("click", function () {
|
|
41
|
+
$("#c-article-directory-content").slideToggle();
|
|
42
|
+
});
|
|
43
|
+
$skip.on("click", function () {
|
|
44
|
+
$(document).scrollTop(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 遍历捕获的目录项
|
|
48
|
+
directories.each(function (i, item) {
|
|
49
|
+
// 进行克隆
|
|
50
|
+
let _item = $(item).clone();
|
|
51
|
+
// 解决懒加载跳转位置问题
|
|
52
|
+
$(item).append(`<a id="directory-${i}"></a>`);
|
|
53
|
+
|
|
54
|
+
// 过滤行内样式
|
|
55
|
+
_item.removeAttr("style");
|
|
56
|
+
_item.removeAttr("align");
|
|
57
|
+
_item.removeAttr("color");
|
|
58
|
+
_item.html($(item).text());
|
|
59
|
+
|
|
60
|
+
// 设置原始元素所在的位置
|
|
61
|
+
// _item.attr("data-skip", ~~$(this).offset().top - 112);
|
|
62
|
+
_item.data("raw", $(item));
|
|
63
|
+
|
|
64
|
+
// 样式设置
|
|
65
|
+
let _tag = $(item)[0].tagName;
|
|
66
|
+
if (__markTags.includes(_tag)) {
|
|
67
|
+
let lv = __markTags.indexOf(_tag) + 1;
|
|
68
|
+
_item.addClass(PREFIX_CLS + lv);
|
|
69
|
+
} else {
|
|
70
|
+
_item.addClass(PREFIX_CLS + 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//追加到目录盒中
|
|
74
|
+
$directory.append(_item);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 进行事件委托
|
|
78
|
+
$directory.on("click", "h1,h2,h3,h4,h5,h6", function () {
|
|
79
|
+
let target = $(this).data("raw").offset().top;
|
|
80
|
+
$(document).scrollTop(target - 112);
|
|
81
|
+
$(this).data("raw").addClass("isScrollFocus");
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
$(this).data("raw").removeClass("isScrollFocus");
|
|
84
|
+
}, 3500);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 不存在目录
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function analystics(from) {
|
|
95
|
+
// 统计各个级别
|
|
96
|
+
for (let key in count) {
|
|
97
|
+
count[key] = $(from).find(key).length;
|
|
98
|
+
}
|
|
99
|
+
// 取前3个级别
|
|
100
|
+
let __c = 0;
|
|
101
|
+
let __markTags = [];
|
|
102
|
+
for (let key in count) {
|
|
103
|
+
if (count[key]) {
|
|
104
|
+
if (__c < MAX_DEPTH) {
|
|
105
|
+
__c += 1;
|
|
106
|
+
__markTags.push(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return __markTags;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default directory;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import $ from "jquery";
|
|
2
|
+
|
|
3
|
+
function renderGallery(vm, selector = ".c-article img") {
|
|
4
|
+
$(selector).each((i, ele) => {
|
|
5
|
+
// 加载全部src(lazyload)
|
|
6
|
+
vm.images.push($(ele).attr("src"));
|
|
7
|
+
// 绑定事件挂钩索引位置
|
|
8
|
+
$(ele).on("click", (e) => {
|
|
9
|
+
vm.gallery_index = i;
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default renderGallery;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* highlight.js 默认支持(不需要额外装库)的语法
|
|
3
|
+
* https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
// 不加参数时默认的
|
|
8
|
+
{ text: "JSON", value: "json" },
|
|
9
|
+
{ text: "Lua", value: "lua" },
|
|
10
|
+
{ text: "HTML, XML", value: "xml" },
|
|
11
|
+
{ text: "Bash", value: "bash" },
|
|
12
|
+
{ text: "JavaScript", value: "javascript" },
|
|
13
|
+
{ text: "Python", value: "python" },
|
|
14
|
+
{ text: "Java", value: "java" },
|
|
15
|
+
{ text: "C", value: "c" },
|
|
16
|
+
{ text: "C#", value: "csharp" },
|
|
17
|
+
{ text: "C++", value: "cpp" },
|
|
18
|
+
|
|
19
|
+
// 其他支持的
|
|
20
|
+
{ text: "1C", value: "1c" },
|
|
21
|
+
{ text: "ABNF", value: "abnf" },
|
|
22
|
+
{ text: "Access logs", value: "accesslog" },
|
|
23
|
+
{ text: "Ada", value: "ada" },
|
|
24
|
+
{ text: "Arduino (C++ w/Arduino libs)", value: "arduino" },
|
|
25
|
+
{ text: "ARM assembler", value: "armasm" },
|
|
26
|
+
{ text: "AVR assembler", value: "avrasm" },
|
|
27
|
+
{ text: "ActionScript", value: "actionscript" },
|
|
28
|
+
{ text: "AngelScript", value: "angelscript" },
|
|
29
|
+
{ text: "Apache", value: "apache" },
|
|
30
|
+
{ text: "AppleScript", value: "applescript" },
|
|
31
|
+
{ text: "Arcade", value: "arcade" },
|
|
32
|
+
{ text: "AsciiDoc", value: "asciidoc" },
|
|
33
|
+
{ text: "AspectJ", value: "aspectj" },
|
|
34
|
+
{ text: "AutoHotkey", value: "autohotkey" },
|
|
35
|
+
{ text: "AutoIt", value: "autoit" },
|
|
36
|
+
{ text: "Awk", value: "awk" },
|
|
37
|
+
{ text: "Basic", value: "basic" },
|
|
38
|
+
{ text: "BNF", value: "bnf" },
|
|
39
|
+
{ text: "Brainfuck", value: "brainfuck" },
|
|
40
|
+
{ text: "C/AL", value: "cal" },
|
|
41
|
+
{ text: "Cache Object Script", value: "cos" },
|
|
42
|
+
{ text: "CMake", value: "cmake" },
|
|
43
|
+
{ text: "Coq", value: "coq" },
|
|
44
|
+
{ text: "CSP", value: "csp" },
|
|
45
|
+
{ text: "CSS", value: "css" },
|
|
46
|
+
{ text: "Cap’n Proto", value: "capnproto" },
|
|
47
|
+
{ text: "Clojure", value: "clojure" },
|
|
48
|
+
{ text: "CoffeeScript", value: "coffeescript" },
|
|
49
|
+
{ text: "Crmsh", value: "crmsh" },
|
|
50
|
+
{ text: "Crystal", value: "crystal" },
|
|
51
|
+
{ text: "D", value: "d" },
|
|
52
|
+
{ text: "Dart", value: "dart" },
|
|
53
|
+
{ text: "Delphi", value: "dpr" },
|
|
54
|
+
{ text: "Diff", value: "diff" },
|
|
55
|
+
{ text: "Django", value: "django" },
|
|
56
|
+
{ text: "DNS Zone file", value: "dns" },
|
|
57
|
+
{ text: "Dockerfile", value: "dockerfile" },
|
|
58
|
+
{ text: "DOS", value: "dos" },
|
|
59
|
+
{ text: "dsconfig", value: "dsconfig" },
|
|
60
|
+
{ text: "DTS (Device Tree)", value: "dts" },
|
|
61
|
+
{ text: "Dust", value: "dust" },
|
|
62
|
+
{ text: "EBNF", value: "ebnf" },
|
|
63
|
+
{ text: "Elixir", value: "elixir" },
|
|
64
|
+
{ text: "Elm", value: "elm" },
|
|
65
|
+
{ text: "Erlang", value: "erlang" },
|
|
66
|
+
{ text: "Excel", value: "excel" },
|
|
67
|
+
{ text: "F#", value: "fsharp" },
|
|
68
|
+
{ text: "FIX", value: "fix" },
|
|
69
|
+
{ text: "Fortran", value: "fortran" },
|
|
70
|
+
{ text: "G-Code", value: "gcode" },
|
|
71
|
+
{ text: "Gams", value: "gams" },
|
|
72
|
+
{ text: "GAUSS", value: "gauss" },
|
|
73
|
+
{ text: "Gherkin", value: "gherkin" },
|
|
74
|
+
{ text: "Go", value: "go" },
|
|
75
|
+
{ text: "Golo", value: "golo" },
|
|
76
|
+
{ text: "Gradle", value: "gradle" },
|
|
77
|
+
{ text: "Groovy", value: "groovy" },
|
|
78
|
+
{ text: "HTTP", value: "http" },
|
|
79
|
+
{ text: "Haml", value: "haml" },
|
|
80
|
+
{ text: "Handlebars", value: "handlebars" },
|
|
81
|
+
{ text: "Haskell", value: "haskell" },
|
|
82
|
+
{ text: "Haxe", value: "haxe" },
|
|
83
|
+
{ text: "Hy", value: "hy" },
|
|
84
|
+
{ text: "Ini, TOML", value: "ini" },
|
|
85
|
+
{ text: "Inform7", value: "inform7" },
|
|
86
|
+
{ text: "IRPF90", value: "irpf90" },
|
|
87
|
+
{ text: "Julia", value: "julia" },
|
|
88
|
+
{ text: "Kotlin", value: "kotlin" },
|
|
89
|
+
{ text: "LaTeX", value: "tex" },
|
|
90
|
+
{ text: "Leaf", value: "leaf" },
|
|
91
|
+
{ text: "Lasso", value: "lasso" },
|
|
92
|
+
{ text: "Less", value: "less" },
|
|
93
|
+
{ text: "LDIF", value: "ldif" },
|
|
94
|
+
{ text: "Lisp", value: "lisp" },
|
|
95
|
+
{ text: "LiveCode Server", value: "livecodeserver" },
|
|
96
|
+
{ text: "LiveScript", value: "livescript" },
|
|
97
|
+
{ text: "Makefile", value: "makefile" },
|
|
98
|
+
{ text: "Markdown", value: "markdown" },
|
|
99
|
+
{ text: "Mathematica", value: "mathematica" },
|
|
100
|
+
{ text: "Matlab", value: "matlab" },
|
|
101
|
+
{ text: "Maxima", value: "maxima" },
|
|
102
|
+
{ text: "Maya Embedded Language", value: "mel" },
|
|
103
|
+
{ text: "Mercury", value: "mercury" },
|
|
104
|
+
{ text: "Mizar", value: "mizar" },
|
|
105
|
+
{ text: "Mojolicious", value: "mojolicious" },
|
|
106
|
+
{ text: "Monkey", value: "monkey" },
|
|
107
|
+
{ text: "Moonscript", value: "moonscript" },
|
|
108
|
+
{ text: "N1QL", value: "n1ql" },
|
|
109
|
+
{ text: "NSIS", value: "nsis" },
|
|
110
|
+
{ text: "Nginx", value: "nginx" },
|
|
111
|
+
{ text: "Nim", value: "nim" },
|
|
112
|
+
{ text: "Nix", value: "nix" },
|
|
113
|
+
{ text: "OCaml", value: "ocaml" },
|
|
114
|
+
{ text: "Objective C", value: "objectivec" },
|
|
115
|
+
{ text: "OpenGL Shading Language", value: "glsl" },
|
|
116
|
+
{ text: "OpenSCAD", value: "openscad" },
|
|
117
|
+
{ text: "Oracle Rules Language", value: "ruleslanguage" },
|
|
118
|
+
{ text: "Oxygene", value: "oxygene" },
|
|
119
|
+
{ text: "PF", value: "pf" },
|
|
120
|
+
{ text: "PHP", value: "php" },
|
|
121
|
+
{ text: "Parser3", value: "parser3" },
|
|
122
|
+
{ text: "Perl", value: "perl" },
|
|
123
|
+
{ text: "Plaintext", value: "plaintext" },
|
|
124
|
+
{ text: "Pony", value: "pony" },
|
|
125
|
+
{ text: "PostgreSQL & PL/pgSQL", value: "pgsql" },
|
|
126
|
+
{ text: "PowerShell", value: "powershell" },
|
|
127
|
+
{ text: "Processing", value: "processing" },
|
|
128
|
+
{ text: "Prolog", value: "prolog" },
|
|
129
|
+
{ text: "Properties", value: "properties" },
|
|
130
|
+
{ text: "Protocol Buffers", value: "protobuf" },
|
|
131
|
+
{ text: "Puppet", value: "puppet" },
|
|
132
|
+
{ text: "Python profiler results", value: "profile" },
|
|
133
|
+
{ text: "Python REPL", value: "python-repl" },
|
|
134
|
+
{ text: "Q", value: "k" },
|
|
135
|
+
{ text: "QML", value: "qml" },
|
|
136
|
+
{ text: "R", value: "r" },
|
|
137
|
+
{ text: "ReasonML", value: "reasonml" },
|
|
138
|
+
{ text: "RenderMan RIB", value: "rib" },
|
|
139
|
+
{ text: "RenderMan RSL", value: "rsl" },
|
|
140
|
+
{ text: "Roboconf", value: "graph" },
|
|
141
|
+
{ text: "Ruby", value: "ruby" },
|
|
142
|
+
{ text: "Rust", value: "rust" },
|
|
143
|
+
{ text: "SAS", value: "SAS" },
|
|
144
|
+
{ text: "SCSS", value: "scss" },
|
|
145
|
+
{ text: "SQL", value: "sql" },
|
|
146
|
+
{ text: "STEP Part 21", value: "p21" },
|
|
147
|
+
{ text: "Scala", value: "scala" },
|
|
148
|
+
{ text: "Scheme", value: "scheme" },
|
|
149
|
+
{ text: "Scilab", value: "scilab" },
|
|
150
|
+
{ text: "Shell", value: "shell" },
|
|
151
|
+
{ text: "Smali", value: "smali" },
|
|
152
|
+
{ text: "Smalltalk", value: "smalltalk" },
|
|
153
|
+
{ text: "SML", value: "sml" },
|
|
154
|
+
{ text: "Stan", value: "stan" },
|
|
155
|
+
{ text: "Stata", value: "stata" },
|
|
156
|
+
{ text: "Stylus", value: "stylus" },
|
|
157
|
+
{ text: "SubUnit", value: "subunit" },
|
|
158
|
+
{ text: "Swift", value: "swift" },
|
|
159
|
+
{ text: "Tcl", value: "tcl" },
|
|
160
|
+
{ text: "Test Anything Protocol", value: "tap" },
|
|
161
|
+
{ text: "Thrift", value: "thrift" },
|
|
162
|
+
{ text: "TP", value: "tp" },
|
|
163
|
+
{ text: "Twig", value: "twig" },
|
|
164
|
+
{ text: "TypeScript", value: "typescript" },
|
|
165
|
+
{ text: "VB.Net", value: "vbnet" },
|
|
166
|
+
{ text: "VBScript", value: "vbscript" },
|
|
167
|
+
{ text: "VHDL", value: "vhdl" },
|
|
168
|
+
{ text: "Vala", value: "vala" },
|
|
169
|
+
{ text: "Verilog", value: "verilog" },
|
|
170
|
+
{ text: "Vim Script", value: "vim" },
|
|
171
|
+
{ text: "X++", value: "axapta" },
|
|
172
|
+
{ text: "x86 Assembly", value: "x86asm" },
|
|
173
|
+
{ text: "XL", value: "xl" },
|
|
174
|
+
{ text: "XQuery", value: "xquery" },
|
|
175
|
+
{ text: "YAML", value: "yml" },
|
|
176
|
+
{ text: "Zephir", value: "zephir" },
|
|
177
|
+
];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// iframeFilter.js
|
|
2
|
+
import { load } from "cheerio";
|
|
3
|
+
|
|
4
|
+
function normalizeWhitelist(list = []) {
|
|
5
|
+
return (Array.isArray(list) ? list : [])
|
|
6
|
+
.map((x) => String(x || "").trim().toLowerCase())
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.map((x) => {
|
|
9
|
+
// 允许传入带协议的,如 https://docs.qq.com
|
|
10
|
+
try {
|
|
11
|
+
if (/^https?:\/\//i.test(x)) return new URL(x).hostname.toLowerCase();
|
|
12
|
+
} catch {
|
|
13
|
+
console.warn(`iframeFilter: 无法解析 whitelist 域名 "${x}",已忽略该项`);
|
|
14
|
+
}
|
|
15
|
+
return x;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 规则:
|
|
21
|
+
* - "docs.qq.com" 精确匹配
|
|
22
|
+
* - "*.qq.com" 只匹配子域(a.qq.com),不匹配根域(qq.com)
|
|
23
|
+
*/
|
|
24
|
+
function matchHost(hostname, whitelist) {
|
|
25
|
+
const host = (hostname || "").toLowerCase();
|
|
26
|
+
if (!host) return false;
|
|
27
|
+
|
|
28
|
+
for (const rule of whitelist) {
|
|
29
|
+
if (!rule) continue;
|
|
30
|
+
|
|
31
|
+
if (rule.startsWith("*.")) {
|
|
32
|
+
const root = rule.slice(2);
|
|
33
|
+
if (root && host.endsWith("." + root)) return true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (host === rule) return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeSrcForParse(src = "") {
|
|
43
|
+
const s = src.trim();
|
|
44
|
+
if (!s) return "";
|
|
45
|
+
// 协议相对://xxx.com/...
|
|
46
|
+
if (s.startsWith("//")) return "https:" + s;
|
|
47
|
+
return s;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getHostnameFromSrc(src = "") {
|
|
51
|
+
const s = normalizeSrcForParse(src);
|
|
52
|
+
if (!s) return "";
|
|
53
|
+
|
|
54
|
+
// 只对白名单校验 http/https 或 // 这种
|
|
55
|
+
if (/^https?:\/\//i.test(s)) {
|
|
56
|
+
try {
|
|
57
|
+
return new URL(s).hostname.toLowerCase();
|
|
58
|
+
} catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {string} html - 含 iframe 的 HTML 字符串(片段也行)
|
|
67
|
+
* @param {string[]} whitelist - 域名白名单,支持 "*.qq.com"
|
|
68
|
+
* @param {object} [options]
|
|
69
|
+
* @param {boolean} [options.keepNonHttpSrc=false] - src 不是 http/https(如 about:blank、data:)是否保留
|
|
70
|
+
*/
|
|
71
|
+
export default function iframeFilter(html, whitelist, options = {}) {
|
|
72
|
+
if (!html) return html;
|
|
73
|
+
|
|
74
|
+
const { keepNonHttpSrc = false } = options;
|
|
75
|
+
|
|
76
|
+
// 没传 whitelist 或传空数组,都视为“不启用白名单过滤”
|
|
77
|
+
const wl = normalizeWhitelist(whitelist || []);
|
|
78
|
+
const enableWhitelist = wl.length > 0;
|
|
79
|
+
|
|
80
|
+
const $ = load(`<root>${html}</root>`, { decodeEntities: false });
|
|
81
|
+
|
|
82
|
+
$("iframe").each((_, el) => {
|
|
83
|
+
const iframe = $(el);
|
|
84
|
+
const srcRaw = iframe.attr("src") ?? "";
|
|
85
|
+
const src = String(srcRaw).trim();
|
|
86
|
+
if (!src) return;
|
|
87
|
+
|
|
88
|
+
// 不启用过滤:直接放行(可选:仍然处理非 http src)
|
|
89
|
+
if (!enableWhitelist) {
|
|
90
|
+
if (!keepNonHttpSrc) {
|
|
91
|
+
const isHttpLike = src.startsWith("//") || /^https?:\/\//i.test(src);
|
|
92
|
+
if (!isHttpLike) iframe.attr("src", "");
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ======= 启用白名单过滤的逻辑(和之前一样) =======
|
|
98
|
+
const isHttpLike = src.startsWith("//") || /^https?:\/\//i.test(src);
|
|
99
|
+
if (!isHttpLike) {
|
|
100
|
+
if (!keepNonHttpSrc) iframe.attr("src", "");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const host = getHostnameFromSrc(src);
|
|
105
|
+
const ok = matchHost(host, wl);
|
|
106
|
+
|
|
107
|
+
if (!ok) iframe.attr("src", "");
|
|
108
|
+
else iframe.attr("src", src);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return $("root").html() || "";
|
|
112
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { load } from "cheerio";
|
|
2
|
+
|
|
3
|
+
function joinDomain(domain, path) {
|
|
4
|
+
const d = String(domain || "").replace(/\/+$/, "");
|
|
5
|
+
const p = String(path || "").replace(/^\/+/, "/");
|
|
6
|
+
return d ? d + p : path;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isAbsoluteLike(src) {
|
|
10
|
+
// 不需要拼 domain 的情况
|
|
11
|
+
return (
|
|
12
|
+
/^(https?:)?\/\//i.test(src) ||
|
|
13
|
+
/^(data:|blob:|mailto:|tel:|javascript:)/i.test(src)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeImgSrc(src, domain) {
|
|
18
|
+
const s = String(src || "").trim();
|
|
19
|
+
if (!s || !domain) return s;
|
|
20
|
+
if (isAbsoluteLike(s)) return s;
|
|
21
|
+
|
|
22
|
+
// 最常见:/uploads/xxx
|
|
23
|
+
if (s.startsWith("/")) return joinDomain(domain, s);
|
|
24
|
+
|
|
25
|
+
// 其它相对路径:uploads/a.png、./a.png
|
|
26
|
+
// 用 URL 做更准确的拼接(domain 需要是带协议的,如 https://cdn.xxx.com)
|
|
27
|
+
try {
|
|
28
|
+
if (/^https?:\/\//i.test(domain)) {
|
|
29
|
+
const base = domain.endsWith("/") ? domain : domain + "/";
|
|
30
|
+
return new URL(s, base).toString();
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
console.warn("URL 拼接图片路径失败:", domain, s);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 兜底:简单拼到根路径
|
|
37
|
+
return joinDomain(domain, "/" + s.replace(/^\.\/+/, ""));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} html 富文本HTML
|
|
42
|
+
* @param {string} domain 可选:CDN/站点域名(如 https://cdn.xxx.com)
|
|
43
|
+
*/
|
|
44
|
+
export default function lazyLoad(html, domain = "") {
|
|
45
|
+
if (!html) return html;
|
|
46
|
+
|
|
47
|
+
const $ = load(`<root>${html}</root>`, { decodeEntities: false });
|
|
48
|
+
|
|
49
|
+
$("img").each((_, el) => {
|
|
50
|
+
const img = $(el);
|
|
51
|
+
|
|
52
|
+
// 固定懒加载:无论如何都加/覆盖
|
|
53
|
+
img.attr("loading", "lazy");
|
|
54
|
+
|
|
55
|
+
// 只有传了 domain 才处理 src 拼接
|
|
56
|
+
if (domain) {
|
|
57
|
+
const src = img.attr("src");
|
|
58
|
+
if (src) img.attr("src", normalizeImgSrc(src, domain));
|
|
59
|
+
|
|
60
|
+
// 如果你有用 data-src 之类,也可以一起处理(可删)
|
|
61
|
+
const dataSrc = img.attr("data-src");
|
|
62
|
+
if (dataSrc) img.attr("data-src", normalizeImgSrc(dataSrc, domain));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return $("root").html() || "";
|
|
67
|
+
}
|