@xcanwin/manyoyo 7.0.26 → 7.1.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/lib/web/AGENTS.md +6 -8
- package/lib/web/frontend/shadcn.html +91 -88
- package/lib/web/server.js +16 -171
- package/package.json +2 -21
- package/lib/web/frontend/app.css +0 -2269
- package/lib/web/frontend/app.html +0 -394
- package/lib/web/frontend/app.js +0 -5352
- package/lib/web/frontend/chat-behavior.js +0 -107
- package/lib/web/frontend/codemirror-entry.js +0 -111
- package/lib/web/frontend/codemirror.bundle.js +0 -31
- package/lib/web/frontend/file-browser.js +0 -800
- package/lib/web/frontend/login.css +0 -208
- package/lib/web/frontend/login.html +0 -25
- package/lib/web/frontend/login.js +0 -74
- package/lib/web/frontend/markdown-renderer.js +0 -316
- package/lib/web/frontend/markdown.css +0 -84
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
--font-ui: "IBM Plex Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
3
|
-
--font-display: "IBM Plex Sans Condensed", "IBM Plex Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
4
|
-
|
|
5
|
-
--bg-canvas: #f3ede4;
|
|
6
|
-
--bg-canvas-alt: #efe4d3;
|
|
7
|
-
--bg-grid: rgba(138, 114, 83, 0.16);
|
|
8
|
-
|
|
9
|
-
--panel: #fffdfa;
|
|
10
|
-
--line: #d9c7ad;
|
|
11
|
-
--line-focus: #c4551f;
|
|
12
|
-
|
|
13
|
-
--text: #1f1a14;
|
|
14
|
-
--muted: #6a5f52;
|
|
15
|
-
|
|
16
|
-
--accent: #c4551f;
|
|
17
|
-
--accent-strong: #9d4418;
|
|
18
|
-
--danger: #bf332d;
|
|
19
|
-
|
|
20
|
-
--shadow-soft: 0 24px 58px rgba(44, 31, 15, 0.2);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
* {
|
|
24
|
-
box-sizing: border-box;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
html,
|
|
28
|
-
body {
|
|
29
|
-
height: 100%;
|
|
30
|
-
touch-action: manipulation;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
body {
|
|
34
|
-
margin: 0;
|
|
35
|
-
min-height: 100vh;
|
|
36
|
-
display: grid;
|
|
37
|
-
place-items: center;
|
|
38
|
-
position: relative;
|
|
39
|
-
overflow: hidden;
|
|
40
|
-
font-family: var(--font-ui);
|
|
41
|
-
color: var(--text);
|
|
42
|
-
background:
|
|
43
|
-
radial-gradient(125% 82% at 6% -6%, #f8f2ea 0%, rgba(248, 242, 234, 0) 57%),
|
|
44
|
-
radial-gradient(95% 66% at 94% 106%, #f2ddc6 0%, rgba(242, 221, 198, 0) 68%),
|
|
45
|
-
linear-gradient(168deg, var(--bg-canvas) 0%, var(--bg-canvas-alt) 100%);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
body::before,
|
|
49
|
-
body::after {
|
|
50
|
-
content: "";
|
|
51
|
-
position: absolute;
|
|
52
|
-
inset: 0;
|
|
53
|
-
pointer-events: none;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
body::before {
|
|
57
|
-
background-image:
|
|
58
|
-
repeating-linear-gradient(
|
|
59
|
-
90deg,
|
|
60
|
-
transparent 0,
|
|
61
|
-
transparent 27px,
|
|
62
|
-
var(--bg-grid) 27px,
|
|
63
|
-
var(--bg-grid) 28px
|
|
64
|
-
);
|
|
65
|
-
opacity: 0.2;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
body::after {
|
|
69
|
-
background-image:
|
|
70
|
-
linear-gradient(148deg, rgba(196, 85, 31, 0.08) 0%, rgba(196, 85, 31, 0) 42%),
|
|
71
|
-
linear-gradient(-148deg, rgba(15, 124, 114, 0.08) 0%, rgba(15, 124, 114, 0) 38%);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.card {
|
|
75
|
-
position: relative;
|
|
76
|
-
z-index: 1;
|
|
77
|
-
width: min(440px, calc(100vw - 30px));
|
|
78
|
-
padding: 26px 24px 20px;
|
|
79
|
-
border: 1px solid var(--line);
|
|
80
|
-
border-radius: 18px;
|
|
81
|
-
background:
|
|
82
|
-
linear-gradient(172deg, rgba(255, 255, 255, 0.97) 0%, rgba(249, 241, 230, 0.97) 100%);
|
|
83
|
-
box-shadow: var(--shadow-soft);
|
|
84
|
-
animation: cardIn 340ms ease both;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.card::before {
|
|
88
|
-
content: "";
|
|
89
|
-
position: absolute;
|
|
90
|
-
left: 0;
|
|
91
|
-
top: 0;
|
|
92
|
-
right: 0;
|
|
93
|
-
height: 4px;
|
|
94
|
-
border-radius: 18px 18px 0 0;
|
|
95
|
-
background: linear-gradient(90deg, #c4551f 0%, #0f7c72 100%);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.badge {
|
|
99
|
-
display: inline-flex;
|
|
100
|
-
align-items: center;
|
|
101
|
-
border-radius: 99px;
|
|
102
|
-
padding: 4px 11px;
|
|
103
|
-
background: #fee8db;
|
|
104
|
-
color: #8b3713;
|
|
105
|
-
font-size: 11px;
|
|
106
|
-
font-weight: 700;
|
|
107
|
-
letter-spacing: 0.95px;
|
|
108
|
-
margin-bottom: 10px;
|
|
109
|
-
text-transform: uppercase;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
h1 {
|
|
113
|
-
margin: 0 0 6px;
|
|
114
|
-
font-family: var(--font-display);
|
|
115
|
-
font-size: 30px;
|
|
116
|
-
line-height: 1.05;
|
|
117
|
-
letter-spacing: 0.4px;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
p {
|
|
121
|
-
margin: 0 0 16px;
|
|
122
|
-
color: var(--muted);
|
|
123
|
-
font-size: 13px;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
label {
|
|
127
|
-
display: block;
|
|
128
|
-
margin: 11px 0 6px;
|
|
129
|
-
font-size: 12px;
|
|
130
|
-
font-weight: 700;
|
|
131
|
-
letter-spacing: 0.2px;
|
|
132
|
-
text-transform: uppercase;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
input {
|
|
136
|
-
width: 100%;
|
|
137
|
-
border: 1px solid var(--line);
|
|
138
|
-
border-radius: 10px;
|
|
139
|
-
padding: 10px 11px;
|
|
140
|
-
font-size: 14px;
|
|
141
|
-
color: var(--text);
|
|
142
|
-
background: #ffffff;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
input:focus {
|
|
146
|
-
outline: none;
|
|
147
|
-
border-color: var(--line-focus);
|
|
148
|
-
box-shadow: 0 0 0 3px rgba(196, 85, 31, 0.15);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
button {
|
|
152
|
-
width: 100%;
|
|
153
|
-
margin-top: 14px;
|
|
154
|
-
border: 1px solid transparent;
|
|
155
|
-
border-radius: 10px;
|
|
156
|
-
padding: 10px 12px;
|
|
157
|
-
font-size: 14px;
|
|
158
|
-
font-weight: 700;
|
|
159
|
-
color: #ffffff;
|
|
160
|
-
background: var(--accent);
|
|
161
|
-
cursor: pointer;
|
|
162
|
-
transition: transform 140ms ease, background-color 140ms ease;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
button:hover {
|
|
166
|
-
transform: translateY(-1px);
|
|
167
|
-
background: var(--accent-strong);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
button:disabled {
|
|
171
|
-
opacity: 0.62;
|
|
172
|
-
cursor: not-allowed;
|
|
173
|
-
transform: none;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
button:focus-visible,
|
|
177
|
-
input:focus-visible {
|
|
178
|
-
outline: 2px solid rgba(196, 85, 31, 0.45);
|
|
179
|
-
outline-offset: 2px;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.error {
|
|
183
|
-
margin-top: 10px;
|
|
184
|
-
min-height: 20px;
|
|
185
|
-
font-size: 13px;
|
|
186
|
-
color: var(--danger);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
@keyframes cardIn {
|
|
190
|
-
from {
|
|
191
|
-
opacity: 0;
|
|
192
|
-
transform: translateY(8px);
|
|
193
|
-
}
|
|
194
|
-
to {
|
|
195
|
-
opacity: 1;
|
|
196
|
-
transform: translateY(0);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
@media (prefers-reduced-motion: reduce) {
|
|
201
|
-
*,
|
|
202
|
-
*::before,
|
|
203
|
-
*::after {
|
|
204
|
-
animation-duration: 0.01ms !important;
|
|
205
|
-
animation-iteration-count: 1 !important;
|
|
206
|
-
transition-duration: 0.01ms !important;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta
|
|
6
|
-
name="viewport"
|
|
7
|
-
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
|
8
|
-
/>
|
|
9
|
-
<title>MANYOYO Web</title>
|
|
10
|
-
<link rel="stylesheet" href="/auth/frontend/login.css" />
|
|
11
|
-
</head>
|
|
12
|
-
<body>
|
|
13
|
-
<form class="card" id="loginForm">
|
|
14
|
-
<div class="badge">MANYOYO</div>
|
|
15
|
-
<h1>Web 登录</h1>
|
|
16
|
-
<label for="username">用户名</label>
|
|
17
|
-
<input id="username" name="username" autocomplete="username" required />
|
|
18
|
-
<label for="password">密码</label>
|
|
19
|
-
<input id="password" name="password" type="password" autocomplete="current-password" required />
|
|
20
|
-
<button type="submit" id="loginBtn">登录</button>
|
|
21
|
-
<div class="error" id="error" role="alert"></div>
|
|
22
|
-
</form>
|
|
23
|
-
<script src="/auth/frontend/login.js"></script>
|
|
24
|
-
</body>
|
|
25
|
-
</html>
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
(function () {
|
|
2
|
-
function preventAccidentalZoom() {
|
|
3
|
-
let lastTouchEnd = 0;
|
|
4
|
-
|
|
5
|
-
document.addEventListener('dblclick', function (event) {
|
|
6
|
-
event.preventDefault();
|
|
7
|
-
}, { passive: false });
|
|
8
|
-
|
|
9
|
-
document.addEventListener('touchstart', function (event) {
|
|
10
|
-
if (event.touches && event.touches.length > 1) {
|
|
11
|
-
event.preventDefault();
|
|
12
|
-
}
|
|
13
|
-
}, { passive: false });
|
|
14
|
-
|
|
15
|
-
document.addEventListener('touchmove', function (event) {
|
|
16
|
-
if (event.touches && event.touches.length > 1) {
|
|
17
|
-
event.preventDefault();
|
|
18
|
-
}
|
|
19
|
-
}, { passive: false });
|
|
20
|
-
|
|
21
|
-
document.addEventListener('touchend', function (event) {
|
|
22
|
-
const now = Date.now();
|
|
23
|
-
if (now - lastTouchEnd <= 320) {
|
|
24
|
-
event.preventDefault();
|
|
25
|
-
}
|
|
26
|
-
lastTouchEnd = now;
|
|
27
|
-
}, { passive: false });
|
|
28
|
-
|
|
29
|
-
['gesturestart', 'gesturechange', 'gestureend'].forEach(function (eventName) {
|
|
30
|
-
document.addEventListener(eventName, function (event) {
|
|
31
|
-
event.preventDefault();
|
|
32
|
-
}, { passive: false });
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
preventAccidentalZoom();
|
|
37
|
-
|
|
38
|
-
const form = document.getElementById('loginForm');
|
|
39
|
-
const userNode = document.getElementById('username');
|
|
40
|
-
const passNode = document.getElementById('password');
|
|
41
|
-
const submitBtn = document.getElementById('loginBtn');
|
|
42
|
-
const errorNode = document.getElementById('error');
|
|
43
|
-
|
|
44
|
-
userNode.focus();
|
|
45
|
-
|
|
46
|
-
form.addEventListener('submit', async function (event) {
|
|
47
|
-
event.preventDefault();
|
|
48
|
-
if (submitBtn.disabled) return;
|
|
49
|
-
errorNode.textContent = '';
|
|
50
|
-
const previousText = submitBtn.textContent;
|
|
51
|
-
submitBtn.disabled = true;
|
|
52
|
-
submitBtn.textContent = '登录中...';
|
|
53
|
-
try {
|
|
54
|
-
const response = await fetch('/auth/login', {
|
|
55
|
-
method: 'POST',
|
|
56
|
-
headers: { 'Content-Type': 'application/json' },
|
|
57
|
-
body: JSON.stringify({
|
|
58
|
-
username: (userNode.value || '').trim(),
|
|
59
|
-
password: passNode.value || ''
|
|
60
|
-
})
|
|
61
|
-
});
|
|
62
|
-
const payload = await response.json().catch(function () { return {}; });
|
|
63
|
-
if (!response.ok) {
|
|
64
|
-
throw new Error(payload.error || '登录失败');
|
|
65
|
-
}
|
|
66
|
-
window.location.href = '/legacy';
|
|
67
|
-
} catch (e) {
|
|
68
|
-
errorNode.textContent = e.message || '登录失败';
|
|
69
|
-
} finally {
|
|
70
|
-
submitBtn.disabled = false;
|
|
71
|
-
submitBtn.textContent = previousText;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
})();
|
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
(function () {
|
|
2
|
-
const MARKDOWN_LINK_PROTOCOL_PATTERN = /^(https?:|mailto:|tel:)/i;
|
|
3
|
-
const MARKDOWN_IMAGE_PROTOCOL_PATTERN = /^(https?:)/i;
|
|
4
|
-
const runtime = {
|
|
5
|
-
configured: false,
|
|
6
|
-
available: false,
|
|
7
|
-
linkGuardBound: false,
|
|
8
|
-
linkOpenHandler: null,
|
|
9
|
-
currentImageResolver: null
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
function escapeHtml(value) {
|
|
13
|
-
return String(value == null ? '' : value)
|
|
14
|
-
.replace(/&/g, '&')
|
|
15
|
-
.replace(/</g, '<')
|
|
16
|
-
.replace(/>/g, '>')
|
|
17
|
-
.replace(/"/g, '"')
|
|
18
|
-
.replace(/'/g, ''');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function normalizeRendererValue(value) {
|
|
22
|
-
if (value && typeof value === 'object') {
|
|
23
|
-
if (typeof value.href === 'string') {
|
|
24
|
-
return value.href;
|
|
25
|
-
}
|
|
26
|
-
if (typeof value.text === 'string') {
|
|
27
|
-
return value.text;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return String(value == null ? '' : value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function sanitizeMarkdownLinkUrl(value) {
|
|
34
|
-
const raw = String(value == null ? '' : value).trim();
|
|
35
|
-
if (!raw) {
|
|
36
|
-
return '';
|
|
37
|
-
}
|
|
38
|
-
if (MARKDOWN_LINK_PROTOCOL_PATTERN.test(raw)) {
|
|
39
|
-
return raw;
|
|
40
|
-
}
|
|
41
|
-
return '';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function sanitizeMarkdownImageUrl(value) {
|
|
45
|
-
const raw = String(value == null ? '' : value).trim();
|
|
46
|
-
if (!raw) {
|
|
47
|
-
return '';
|
|
48
|
-
}
|
|
49
|
-
if (raw[0] === '/') {
|
|
50
|
-
return raw.startsWith('//') ? '' : raw;
|
|
51
|
-
}
|
|
52
|
-
if (raw.startsWith('./') || raw.startsWith('../')) {
|
|
53
|
-
return raw;
|
|
54
|
-
}
|
|
55
|
-
if (MARKDOWN_IMAGE_PROTOCOL_PATTERN.test(raw)) {
|
|
56
|
-
return raw;
|
|
57
|
-
}
|
|
58
|
-
return '';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function getRendererToken(value) {
|
|
62
|
-
return value && typeof value === 'object' ? value : null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function renderInlineTokens(rendererContext, token, fallbackText) {
|
|
66
|
-
if (
|
|
67
|
-
token
|
|
68
|
-
&& Array.isArray(token.tokens)
|
|
69
|
-
&& rendererContext
|
|
70
|
-
&& rendererContext.parser
|
|
71
|
-
&& typeof rendererContext.parser.parseInline === 'function'
|
|
72
|
-
) {
|
|
73
|
-
try {
|
|
74
|
-
return String(rendererContext.parser.parseInline(token.tokens) || '');
|
|
75
|
-
} catch (e) {
|
|
76
|
-
// ignore and fallback to plain text below
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (token && typeof token.text === 'string') {
|
|
80
|
-
return escapeHtml(token.text);
|
|
81
|
-
}
|
|
82
|
-
return escapeHtml(fallbackText || '');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function buildSafeAnchorHtml(href, title, content, extraAttrs) {
|
|
86
|
-
let output = '<a href="' + escapeHtml(href) + '" target="_blank" rel="noopener noreferrer"'
|
|
87
|
-
+ ' referrerpolicy="no-referrer" data-safe-external-link="true"'
|
|
88
|
-
+ ' data-safe-href="' + escapeHtml(href) + '"';
|
|
89
|
-
if (title) {
|
|
90
|
-
output += ' title="' + escapeHtml(title) + '"';
|
|
91
|
-
}
|
|
92
|
-
if (extraAttrs) {
|
|
93
|
-
output += extraAttrs;
|
|
94
|
-
}
|
|
95
|
-
output += '>' + content + '</a>';
|
|
96
|
-
return output;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function openExternalLinkWithNoReferrer(href) {
|
|
100
|
-
const url = String(href || '').trim();
|
|
101
|
-
if (!url || typeof document === 'undefined' || !document || typeof document.createElement !== 'function') {
|
|
102
|
-
if (typeof window.open === 'function') {
|
|
103
|
-
const opened = window.open(url, '_blank', 'noopener,noreferrer');
|
|
104
|
-
if (opened) {
|
|
105
|
-
opened.opener = null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const anchor = document.createElement('a');
|
|
112
|
-
anchor.href = url;
|
|
113
|
-
anchor.target = '_blank';
|
|
114
|
-
anchor.rel = 'noopener noreferrer';
|
|
115
|
-
anchor.referrerPolicy = 'no-referrer';
|
|
116
|
-
anchor.style.display = 'none';
|
|
117
|
-
document.body.appendChild(anchor);
|
|
118
|
-
try {
|
|
119
|
-
if (typeof anchor.click === 'function') {
|
|
120
|
-
anchor.click();
|
|
121
|
-
} else if (typeof window.open === 'function') {
|
|
122
|
-
const opened = window.open(url, '_blank', 'noopener,noreferrer');
|
|
123
|
-
if (opened) {
|
|
124
|
-
opened.opener = null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
} finally {
|
|
128
|
-
if (anchor.parentNode && typeof anchor.parentNode.removeChild === 'function') {
|
|
129
|
-
anchor.parentNode.removeChild(anchor);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function requestExternalLinkOpen(href) {
|
|
135
|
-
const url = String(href || '').trim();
|
|
136
|
-
if (!url) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (typeof runtime.linkOpenHandler === 'function') {
|
|
140
|
-
runtime.linkOpenHandler(url);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const shouldOpen = typeof window.confirm === 'function'
|
|
145
|
-
? window.confirm('即将打开外部链接:\n' + url + '\n\n确认继续打开?')
|
|
146
|
-
: true;
|
|
147
|
-
if (!shouldOpen) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
openExternalLinkWithNoReferrer(url);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function bindDocumentLinkGuard() {
|
|
155
|
-
if (runtime.linkGuardBound || typeof document === 'undefined' || !document || typeof document.addEventListener !== 'function') {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
document.addEventListener('click', function (event) {
|
|
160
|
-
const target = event && event.target;
|
|
161
|
-
if (!target || typeof target.closest !== 'function') {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
const link = target.closest('a[data-safe-external-link="true"]');
|
|
165
|
-
if (!link) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (event.preventDefault) {
|
|
169
|
-
event.preventDefault();
|
|
170
|
-
}
|
|
171
|
-
if (event.stopPropagation) {
|
|
172
|
-
event.stopPropagation();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const href = String(
|
|
176
|
-
(typeof link.getAttribute === 'function' && (link.getAttribute('data-safe-href') || link.getAttribute('href')))
|
|
177
|
-
|| link.href
|
|
178
|
-
|| ''
|
|
179
|
-
).trim();
|
|
180
|
-
if (!href) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
requestExternalLinkOpen(href);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
runtime.linkGuardBound = true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function getMarkedApi() {
|
|
191
|
-
const api = window.marked;
|
|
192
|
-
if (!api || typeof api.parse !== 'function') {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
return api;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function ensureRendererConfigured() {
|
|
199
|
-
if (runtime.configured) {
|
|
200
|
-
return runtime.available;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const markedApi = getMarkedApi();
|
|
204
|
-
if (!markedApi || typeof markedApi.Renderer !== 'function' || typeof markedApi.use !== 'function') {
|
|
205
|
-
runtime.configured = true;
|
|
206
|
-
runtime.available = false;
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const renderer = new markedApi.Renderer();
|
|
212
|
-
renderer.html = function (html) {
|
|
213
|
-
const token = getRendererToken(html);
|
|
214
|
-
return escapeHtml(token ? token.text : html);
|
|
215
|
-
};
|
|
216
|
-
renderer.link = function (href, title, text) {
|
|
217
|
-
const token = getRendererToken(href);
|
|
218
|
-
const rawHref = token ? token.href : normalizeRendererValue(href);
|
|
219
|
-
const rawTitle = token ? token.title : title;
|
|
220
|
-
const safeHref = sanitizeMarkdownLinkUrl(rawHref);
|
|
221
|
-
const safeText = renderInlineTokens(this, token, text)
|
|
222
|
-
// [P1-02] 移除 marked 已渲染链接文本中的 on* 事件属性,防止内联 HTML 注入 XSS
|
|
223
|
-
.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, '');
|
|
224
|
-
if (!safeHref) {
|
|
225
|
-
return safeText || escapeHtml(token ? token.text : text || '');
|
|
226
|
-
}
|
|
227
|
-
return buildSafeAnchorHtml(safeHref, rawTitle, safeText || escapeHtml(safeHref));
|
|
228
|
-
};
|
|
229
|
-
// [P1-01] 重写 image 渲染器:
|
|
230
|
-
// - 外部 http/https 图片转为可点击链接,避免浏览器自动发起外部请求(追踪像素风险)
|
|
231
|
-
// - 相对路径图片正常渲染为 <img>
|
|
232
|
-
// - 危险协议(javascript:/data: 等)降级为纯文本
|
|
233
|
-
renderer.image = function (href, title, text) {
|
|
234
|
-
const token = getRendererToken(href);
|
|
235
|
-
const rawHref = token ? token.href : normalizeRendererValue(href);
|
|
236
|
-
const rawTitle = token ? token.title : title;
|
|
237
|
-
const safeHref = sanitizeMarkdownImageUrl(rawHref);
|
|
238
|
-
const safeText = renderInlineTokens(this, token, text)
|
|
239
|
-
.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, '');
|
|
240
|
-
if (!safeHref) {
|
|
241
|
-
return safeText || escapeHtml(token ? token.text : text || '');
|
|
242
|
-
}
|
|
243
|
-
// 外部绝对 URL:转为链接,用户主动决定是否访问
|
|
244
|
-
if (/^https?:/i.test(safeHref)) {
|
|
245
|
-
return buildSafeAnchorHtml(
|
|
246
|
-
safeHref,
|
|
247
|
-
rawTitle,
|
|
248
|
-
'[\uD83D\uDDBC\uFE0F点击查看图片:' + (safeText || escapeHtml(safeHref)) + ']'
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
// 相对路径:交给调用方按当前渲染上下文解析为可访问的 URL(未提供 resolver 时原样使用)
|
|
252
|
-
const resolvedHref = typeof runtime.currentImageResolver === 'function'
|
|
253
|
-
? runtime.currentImageResolver(safeHref)
|
|
254
|
-
: safeHref;
|
|
255
|
-
if (!resolvedHref) {
|
|
256
|
-
return safeText || escapeHtml(token ? token.text : text || '');
|
|
257
|
-
}
|
|
258
|
-
let output = '<img src="' + escapeHtml(resolvedHref) + '" alt="' + escapeHtml(text || '') + '"';
|
|
259
|
-
if (rawTitle) {
|
|
260
|
-
output += ' title="' + escapeHtml(rawTitle) + '"';
|
|
261
|
-
}
|
|
262
|
-
return output + '>';
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
markedApi.use({
|
|
266
|
-
gfm: true,
|
|
267
|
-
breaks: true,
|
|
268
|
-
renderer
|
|
269
|
-
});
|
|
270
|
-
runtime.available = true;
|
|
271
|
-
} catch (e) {
|
|
272
|
-
runtime.available = false;
|
|
273
|
-
}
|
|
274
|
-
runtime.configured = true;
|
|
275
|
-
return runtime.available;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function shouldRenderMessage(msg) {
|
|
279
|
-
return Boolean(msg && msg.mode === 'agent' && msg.role === 'assistant');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function render(content, options) {
|
|
283
|
-
const source = String(content == null ? '' : content);
|
|
284
|
-
if (!source) {
|
|
285
|
-
return '';
|
|
286
|
-
}
|
|
287
|
-
if (!ensureRendererConfigured()) {
|
|
288
|
-
return '';
|
|
289
|
-
}
|
|
290
|
-
const markedApi = getMarkedApi();
|
|
291
|
-
if (!markedApi) {
|
|
292
|
-
return '';
|
|
293
|
-
}
|
|
294
|
-
runtime.currentImageResolver = options && typeof options.imageUrlResolver === 'function'
|
|
295
|
-
? options.imageUrlResolver
|
|
296
|
-
: null;
|
|
297
|
-
try {
|
|
298
|
-
return String(markedApi.parse(source) || '');
|
|
299
|
-
} catch (e) {
|
|
300
|
-
return '';
|
|
301
|
-
} finally {
|
|
302
|
-
runtime.currentImageResolver = null;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
window.ManyoyoMarkdown = {
|
|
307
|
-
shouldRenderMessage,
|
|
308
|
-
render,
|
|
309
|
-
openExternalLink: openExternalLinkWithNoReferrer,
|
|
310
|
-
setLinkOpenHandler: function (handler) {
|
|
311
|
-
runtime.linkOpenHandler = typeof handler === 'function' ? handler : null;
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
bindDocumentLinkGuard();
|
|
316
|
-
}());
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
.md-content {
|
|
2
|
-
color: var(--text);
|
|
3
|
-
font-size: 13px;
|
|
4
|
-
line-height: 1.6;
|
|
5
|
-
word-break: break-word;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.md-content > :first-child {
|
|
9
|
-
margin-top: 0;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.md-content > :last-child {
|
|
13
|
-
margin-bottom: 0;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.md-content p,
|
|
17
|
-
.md-content ul,
|
|
18
|
-
.md-content ol,
|
|
19
|
-
.md-content blockquote,
|
|
20
|
-
.md-content pre,
|
|
21
|
-
.md-content table {
|
|
22
|
-
margin: 0.6em 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.md-content ul,
|
|
26
|
-
.md-content ol {
|
|
27
|
-
padding-left: 1.4em;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.md-content code {
|
|
31
|
-
font-family: var(--font-mono);
|
|
32
|
-
font-size: 12px;
|
|
33
|
-
padding: 1px 5px;
|
|
34
|
-
border-radius: 6px;
|
|
35
|
-
background: rgba(194, 149, 79, 0.16);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.md-content pre {
|
|
39
|
-
border-radius: 8px;
|
|
40
|
-
padding: 9px 10px;
|
|
41
|
-
border: 1px solid #e6d2b7;
|
|
42
|
-
background: rgba(255, 244, 224, 0.72);
|
|
43
|
-
overflow-x: auto;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.md-content pre code {
|
|
47
|
-
background: transparent;
|
|
48
|
-
border-radius: 0;
|
|
49
|
-
padding: 0;
|
|
50
|
-
font-size: 12px;
|
|
51
|
-
line-height: 1.5;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.md-content blockquote {
|
|
55
|
-
margin-left: 0;
|
|
56
|
-
padding-left: 10px;
|
|
57
|
-
border-left: 3px solid #dcb788;
|
|
58
|
-
color: #66492d;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.md-content table {
|
|
62
|
-
border-collapse: collapse;
|
|
63
|
-
width: 100%;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.md-content th,
|
|
67
|
-
.md-content td {
|
|
68
|
-
border: 1px solid #e6d2b7;
|
|
69
|
-
padding: 4px 6px;
|
|
70
|
-
text-align: left;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.md-content a {
|
|
74
|
-
color: #8a4f17;
|
|
75
|
-
text-decoration-line: underline;
|
|
76
|
-
text-decoration-thickness: 2px;
|
|
77
|
-
text-underline-offset: 0.16em;
|
|
78
|
-
text-decoration-color: rgba(138, 79, 23, 0.8);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.md-content a:hover,
|
|
82
|
-
.md-content a:focus-visible {
|
|
83
|
-
text-decoration-color: #8a4f17;
|
|
84
|
-
}
|