@hailin-zheng/editor-core 2.2.1 → 2.2.2
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/editor.css +52 -1
- package/index-cjs.js +125 -105
- package/index-cjs.js.map +1 -1
- package/index.js +125 -105
- package/index.js.map +1 -1
- package/med_editor/framework/doc-layout/document-arrange.d.ts +6 -0
- package/med_editor/framework/document-context.d.ts +3 -6
- package/med_editor/framework/document-paginator.d.ts +5 -0
- package/med_editor/framework/element-props.d.ts +0 -1
- package/package.json +1 -1
package/editor.css
CHANGED
@@ -17,7 +17,20 @@
|
|
17
17
|
}
|
18
18
|
|
19
19
|
.data-list > div {
|
20
|
-
|
20
|
+
padding: 10px 6px;
|
21
|
+
display: flex;
|
22
|
+
align-items: center;
|
23
|
+
gap: 10px;
|
24
|
+
border-bottom: 1px solid #ccc;
|
25
|
+
cursor: default;
|
26
|
+
}
|
27
|
+
|
28
|
+
.data-list > div:hover {
|
29
|
+
background-color: #ccc;
|
30
|
+
}
|
31
|
+
|
32
|
+
.data-list > div:last-child {
|
33
|
+
border-bottom: none; /* 去掉最后一个列表项的底部边框线 */
|
21
34
|
}
|
22
35
|
|
23
36
|
@keyframes hover-color {
|
@@ -163,6 +176,44 @@
|
|
163
176
|
border: #c9e2f9;
|
164
177
|
background: #c9e2f9;
|
165
178
|
}
|
179
|
+
/* 样式化 radio box 的外观 */
|
180
|
+
.editor-list-radiobox {
|
181
|
+
width: 16px;
|
182
|
+
height: 16px;
|
183
|
+
border: 1px solid #000;
|
184
|
+
border-radius: 50%;
|
185
|
+
cursor: pointer;
|
186
|
+
display: inline-block;
|
187
|
+
position: relative;
|
188
|
+
}
|
189
|
+
/* 选中状态的样式 */
|
190
|
+
.editor-list-radiobox.checked::after {
|
191
|
+
content: "";
|
192
|
+
width: 8px;
|
193
|
+
height: 8px;
|
194
|
+
background-color: #007bff;
|
195
|
+
border-radius: 50%;
|
196
|
+
position: absolute;
|
197
|
+
top: 50%;
|
198
|
+
left: 50%;
|
199
|
+
transform: translate(-50%, -50%);
|
200
|
+
}
|
201
|
+
/* 样式化 checkbox 的外观 */
|
202
|
+
.editor-list-checkbox {
|
203
|
+
width: 16px;
|
204
|
+
height: 16px;
|
205
|
+
border: 1px solid #000;
|
206
|
+
cursor: pointer;
|
207
|
+
}
|
208
|
+
/* 选中状态的样式 */
|
209
|
+
.editor-list-checkbox.checked::after {
|
210
|
+
content: "✔";
|
211
|
+
font-size: 14px;
|
212
|
+
color: #007bff;
|
213
|
+
display: block;
|
214
|
+
text-align: center;
|
215
|
+
line-height: 16px;
|
216
|
+
}
|
166
217
|
|
167
218
|
.decorate-container {
|
168
219
|
position: absolute;
|
package/index-cjs.js
CHANGED
@@ -2135,7 +2135,6 @@ class DocumentProps extends INotifyPropertyChanged {
|
|
2135
2135
|
createUserId;
|
2136
2136
|
createUserName;
|
2137
2137
|
createDate;
|
2138
|
-
scripts;
|
2139
2138
|
columns = 1;
|
2140
2139
|
version;
|
2141
2140
|
clone(dest) {
|
@@ -2146,7 +2145,6 @@ class DocumentProps extends INotifyPropertyChanged {
|
|
2146
2145
|
clone.padding = this.padding.clone();
|
2147
2146
|
clone.headerLine = this.headerLine;
|
2148
2147
|
clone.footerLine = this.footerLine;
|
2149
|
-
clone.scripts = this.scripts;
|
2150
2148
|
clone.createUserId = this.createUserId;
|
2151
2149
|
clone.createUserName = this.createUserName;
|
2152
2150
|
clone.createDate = this.createDate;
|
@@ -2166,9 +2164,6 @@ class DocumentProps extends INotifyPropertyChanged {
|
|
2166
2164
|
createUserName: this.createUserName,
|
2167
2165
|
createUserId: this.createUserId,
|
2168
2166
|
};
|
2169
|
-
if (this.scripts) {
|
2170
|
-
props['scripts'] = this.scripts;
|
2171
|
-
}
|
2172
2167
|
if (this.orient && this.orient !== 'portrait') {
|
2173
2168
|
props['orient'] = this.orient;
|
2174
2169
|
}
|
@@ -3720,7 +3715,6 @@ class DocumentFactory extends ElementFactory {
|
|
3720
3715
|
docProps.padding = new PaddingProps(props.padding.top, props.padding.bottom, props.padding.left, props.padding.right);
|
3721
3716
|
docProps.headerLine = props.headerLine ?? 12;
|
3722
3717
|
docProps.footerLine = props.footerLine ?? 12;
|
3723
|
-
docProps.scripts = props.scripts;
|
3724
3718
|
docProps.createUserId = props.createUserId;
|
3725
3719
|
docProps.createUserName = props.createUserName;
|
3726
3720
|
docProps.createDate = props.createDate;
|
@@ -6931,7 +6925,7 @@ class CommentElement extends LeafElement {
|
|
6931
6925
|
constructor() {
|
6932
6926
|
super('comm');
|
6933
6927
|
//this.isDecorate = true;
|
6934
|
-
this.disableClick = true;
|
6928
|
+
//this.disableClick = true;
|
6935
6929
|
this.props = new CommProps();
|
6936
6930
|
this.color = CommonUtil.randomRgbColor(0.5);
|
6937
6931
|
}
|
@@ -12939,54 +12933,6 @@ class PaintContent {
|
|
12939
12933
|
}
|
12940
12934
|
}
|
12941
12935
|
|
12942
|
-
class DocumentEvalFunc {
|
12943
|
-
docCtx;
|
12944
|
-
constructor(docCtx) {
|
12945
|
-
this.docCtx = docCtx;
|
12946
|
-
}
|
12947
|
-
scriptsFunc;
|
12948
|
-
/**
|
12949
|
-
* 实例化动态脚本
|
12950
|
-
*/
|
12951
|
-
initScripts(scripts) {
|
12952
|
-
this.destroyScripts();
|
12953
|
-
if (scripts) {
|
12954
|
-
try {
|
12955
|
-
const func = new Function("docCtx", scripts);
|
12956
|
-
this.scriptsFunc = func(this.docCtx);
|
12957
|
-
}
|
12958
|
-
catch (e) {
|
12959
|
-
console.error("自定义标本解析错误", e);
|
12960
|
-
}
|
12961
|
-
}
|
12962
|
-
// const func = (docCtx: DocumentContext) => {
|
12963
|
-
// const sexELe = docCtx.getControlById('NqoYI')
|
12964
|
-
// const dyEle = docCtx.getControlById('gTuBI');
|
12965
|
-
// return () => {
|
12966
|
-
// if (sexELe && dyEle) {
|
12967
|
-
// const sexValue = sexELe.getValue();
|
12968
|
-
// const dyValue = sexValue === '1' ? '男的吗' : sexValue === '2' ? '女的吗' : '难道是人妖吗';
|
12969
|
-
// dyEle.setValue(dyValue);
|
12970
|
-
// }
|
12971
|
-
// };
|
12972
|
-
// };
|
12973
|
-
}
|
12974
|
-
/**
|
12975
|
-
* 销毁动态脚本实例
|
12976
|
-
*/
|
12977
|
-
destroyScripts() {
|
12978
|
-
if (this.scriptsFunc) {
|
12979
|
-
this.scriptsFunc = null;
|
12980
|
-
}
|
12981
|
-
}
|
12982
|
-
/**
|
12983
|
-
* 触发动态脚本
|
12984
|
-
*/
|
12985
|
-
invokedScripts() {
|
12986
|
-
this.scriptsFunc?.();
|
12987
|
-
}
|
12988
|
-
}
|
12989
|
-
|
12990
12936
|
/**
|
12991
12937
|
* 当前打开的文档的上下文信息,当前文档所有的属性设置都暴露在上下文中
|
12992
12938
|
*/
|
@@ -12997,16 +12943,17 @@ class EditorContext {
|
|
12997
12943
|
cursorRect;
|
12998
12944
|
_document;
|
12999
12945
|
syncRefresh;
|
13000
|
-
dynamicFunc;
|
13001
12946
|
docChange;
|
13002
12947
|
clearPrevDocCb;
|
13003
12948
|
//绘制结束之后回调函数
|
13004
|
-
//nextViewFn!: (() => void) | null;
|
13005
12949
|
nextViewFns = [];
|
12950
|
+
//批注元素存在的标志,用于判断文档空间进行布局
|
12951
|
+
commentFlag = false;
|
12952
|
+
//留痕元素存在的标志,用于判断文档空间进行布局
|
12953
|
+
trackFlag = false;
|
13006
12954
|
constructor(selectionState, viewOptions) {
|
13007
12955
|
this.selectionState = selectionState;
|
13008
12956
|
this.viewOptions = viewOptions;
|
13009
|
-
this.dynamicFunc = new DocumentEvalFunc(this);
|
13010
12957
|
//this.imageLoader = new DocumentImagesLoader();
|
13011
12958
|
this.selectionState.onChangedEvent.subscribe(() => {
|
13012
12959
|
this.syncRefresh?.();
|
@@ -13026,7 +12973,6 @@ class EditorContext {
|
|
13026
12973
|
set document(value) {
|
13027
12974
|
this.clearPrevDocCb?.();
|
13028
12975
|
this._document = value;
|
13029
|
-
this.initScripts();
|
13030
12976
|
// this.refSub = this._document.refreshSubject.subscribe((data) => {
|
13031
12977
|
// data = data ?? 'content';
|
13032
12978
|
// this.isDirty = this.isDirty || data === 'content';
|
@@ -13054,8 +13000,6 @@ class EditorContext {
|
|
13054
13000
|
}
|
13055
13001
|
clear() {
|
13056
13002
|
this.selectionState.clear();
|
13057
|
-
//this.imageLoader.clear();
|
13058
|
-
this.dynamicFunc.destroyScripts();
|
13059
13003
|
this.isDirty = false;
|
13060
13004
|
//this.clearEleDepMaps();
|
13061
13005
|
}
|
@@ -13107,12 +13051,6 @@ class EditorContext {
|
|
13107
13051
|
this.document.viewOptions.textRowLineMode = !this.document.viewOptions.textRowLineMode;
|
13108
13052
|
this.syncRefresh();
|
13109
13053
|
}
|
13110
|
-
/**
|
13111
|
-
* 实例化动态脚本
|
13112
|
-
*/
|
13113
|
-
initScripts() {
|
13114
|
-
this.dynamicFunc.initScripts(this.document.props.scripts);
|
13115
|
-
}
|
13116
13054
|
/**
|
13117
13055
|
* 替换数据元
|
13118
13056
|
*/
|
@@ -13131,6 +13069,15 @@ class EditorContext {
|
|
13131
13069
|
}
|
13132
13070
|
return this._document.modifyFlag === exports.ModifyFlag.None ? 'appearance' : 'content';
|
13133
13071
|
}
|
13072
|
+
adaptiveScale() {
|
13073
|
+
if (this.viewOptions.pageLayoutMode !== 'fit-page') {
|
13074
|
+
return this.viewOptions.scale;
|
13075
|
+
}
|
13076
|
+
const docWidth = this.viewOptions.contentWidth;
|
13077
|
+
const viewWidth = this.viewOptions.viewSettings.width;
|
13078
|
+
const availableWidth = viewWidth * 0.9;
|
13079
|
+
return Math.round(availableWidth * 100 / docWidth) / 100;
|
13080
|
+
}
|
13134
13081
|
}
|
13135
13082
|
/**
|
13136
13083
|
* 文档上下文
|
@@ -14296,9 +14243,7 @@ class DocumentArrange {
|
|
14296
14243
|
execute: this.execute,
|
14297
14244
|
createParaFn: () => this.createDefaultPara()
|
14298
14245
|
};
|
14299
|
-
|
14300
|
-
this.clearPaintCache(doc, data);
|
14301
|
-
//this.docCtx.viewOptions.showReviewWindow = this.docCtx.document.commentsContainerElement.markPairs.length > 0;
|
14246
|
+
this.reset(data);
|
14302
14247
|
const docRenders = this.arrangeDoc();
|
14303
14248
|
this.setMeasureCompletedModifyFlag(doc);
|
14304
14249
|
this.cacheDocRenders(docRenders);
|
@@ -14306,7 +14251,17 @@ class DocumentArrange {
|
|
14306
14251
|
return docRenders;
|
14307
14252
|
});
|
14308
14253
|
}
|
14309
|
-
|
14254
|
+
/**
|
14255
|
+
* 重置文档测量的相关信息
|
14256
|
+
* @param data
|
14257
|
+
* @private
|
14258
|
+
*/
|
14259
|
+
reset(data) {
|
14260
|
+
this.docCtx.document.clearMarkItems();
|
14261
|
+
this.docCtx.trackFlag = false;
|
14262
|
+
this.docCtx.commentFlag = false;
|
14263
|
+
this.clearPaintCache(data.doc, data);
|
14264
|
+
}
|
14310
14265
|
arrangeDoc() {
|
14311
14266
|
const doc = this.docCtx.document;
|
14312
14267
|
const docRender = doc.createRenderObject();
|
@@ -14850,8 +14805,12 @@ class DocumentArrange {
|
|
14850
14805
|
}
|
14851
14806
|
identifyComment(ele) {
|
14852
14807
|
if (ele instanceof CommentElement) {
|
14808
|
+
this.docCtx.commentFlag = true;
|
14853
14809
|
this.docCtx.document.identifyCommMark(ele);
|
14854
14810
|
}
|
14811
|
+
else if (ele instanceof TrackRunElement) {
|
14812
|
+
this.docCtx.trackFlag = true;
|
14813
|
+
}
|
14855
14814
|
}
|
14856
14815
|
cacheDoc;
|
14857
14816
|
cacheDocRenders(docs) {
|
@@ -14932,8 +14891,31 @@ class DocumentPaginator {
|
|
14932
14891
|
this.docContainer.rect.width = this.viewOptions.docPageSettings.width;
|
14933
14892
|
const newMeasure = new DocumentArrange(this.docCtx, this.renderContext, this.seo);
|
14934
14893
|
this.docPages = newMeasure.measureDoc();
|
14894
|
+
this.adjustTipLayoutWidth();
|
14935
14895
|
this.layoutPages();
|
14936
14896
|
}
|
14897
|
+
/**
|
14898
|
+
* 处理计算提示框布局宽度
|
14899
|
+
* @private
|
14900
|
+
*/
|
14901
|
+
adjustTipLayoutWidth() {
|
14902
|
+
let layoutFlag = false;
|
14903
|
+
if (this.docCtx.trackFlag && this.docCtx.viewOptions.showTrackChangesTip || this.docCtx.commentFlag) {
|
14904
|
+
layoutFlag = true;
|
14905
|
+
}
|
14906
|
+
if (layoutFlag) {
|
14907
|
+
if (this.viewOptions.reviewWindowWidth === 0) {
|
14908
|
+
this.viewOptions.reviewWindowWidth = 250;
|
14909
|
+
this.viewOptions.scale = this.docCtx.adaptiveScale();
|
14910
|
+
}
|
14911
|
+
}
|
14912
|
+
else {
|
14913
|
+
if (this.viewOptions.reviewWindowWidth > 0) {
|
14914
|
+
this.viewOptions.reviewWindowWidth = 0;
|
14915
|
+
this.viewOptions.scale = this.docCtx.adaptiveScale();
|
14916
|
+
}
|
14917
|
+
}
|
14918
|
+
}
|
14937
14919
|
/**
|
14938
14920
|
* 文档页面显示布局
|
14939
14921
|
*/
|
@@ -16941,7 +16923,7 @@ class DocumentChange {
|
|
16941
16923
|
if (res && res.isCancel) {
|
16942
16924
|
return;
|
16943
16925
|
}
|
16944
|
-
this.docComment.syncUpdateComments();
|
16926
|
+
//this.docComment.syncUpdateComments();
|
16945
16927
|
if (collapsed) {
|
16946
16928
|
this.onBackspaceElement(startControl, startOffset);
|
16947
16929
|
}
|
@@ -20361,8 +20343,6 @@ class DocEditor {
|
|
20361
20343
|
trackChangeState = true;
|
20362
20344
|
flushToSchedule() {
|
20363
20345
|
if (this.docCtx.refreshType === 'content') {
|
20364
|
-
//触发动态脚本
|
20365
|
-
this.docCtx.dynamicFunc.invokedScripts();
|
20366
20346
|
this.triggerDocChange();
|
20367
20347
|
}
|
20368
20348
|
if (this.flushTask) {
|
@@ -20721,14 +20701,7 @@ class DocEditor {
|
|
20721
20701
|
* @private
|
20722
20702
|
*/
|
20723
20703
|
adaptiveScale() {
|
20724
|
-
|
20725
|
-
return;
|
20726
|
-
}
|
20727
|
-
const docWidth = this.docCtx.viewOptions.docPageSettings.width;
|
20728
|
-
const viewWidth = this.docCtx.viewOptions.viewSettings.width;
|
20729
|
-
const availableWidth = viewWidth * 0.9;
|
20730
|
-
const scale = Math.round(availableWidth * 100 / docWidth) / 100;
|
20731
|
-
this.viewOptions.scale = scale;
|
20704
|
+
this.viewOptions.scale = this.docCtx.adaptiveScale();
|
20732
20705
|
}
|
20733
20706
|
/**
|
20734
20707
|
* 缩放视图
|
@@ -21327,12 +21300,20 @@ class DocEditor {
|
|
21327
21300
|
},
|
21328
21301
|
children: []
|
21329
21302
|
};
|
21303
|
+
const scaleFitContainer = {
|
21304
|
+
sel: 'div#scale-fit-container',
|
21305
|
+
data: {
|
21306
|
+
style: { overflow: 'hidden', height: '0px', }
|
21307
|
+
},
|
21308
|
+
children: [docContent]
|
21309
|
+
};
|
21330
21310
|
if (!this.documentPaginator?.docContainer) {
|
21331
|
-
return
|
21311
|
+
return scaleFitContainer;
|
21332
21312
|
}
|
21333
21313
|
const tipsContainer = this.createChangeTipContainer();
|
21334
21314
|
this.tipContainer = tipsContainer;
|
21335
21315
|
docContent.data.style.height = this.documentPaginator.getDocumentContainerHeight().height + 'px';
|
21316
|
+
scaleFitContainer.data.style['height'] = this.documentPaginator.getDocumentContainerHeight().height * this.viewOptions.scale + 'px';
|
21336
21317
|
const docRenders = this.documentPaginator.docContainer.getItems();
|
21337
21318
|
const svgGenerator = new DocumentSvg(this.viewOptions, this.selectionOverlays, this.renderContext);
|
21338
21319
|
const vNode = svgGenerator.getHTMLVNode(docRenders);
|
@@ -21340,12 +21321,12 @@ class DocEditor {
|
|
21340
21321
|
children.push(tipsContainer);
|
21341
21322
|
children.push(...vNode);
|
21342
21323
|
tipsContainer.children?.push(...svgGenerator.changeTips);
|
21343
|
-
this.updateTipLayoutWidth();
|
21324
|
+
//this.updateTipLayoutWidth();
|
21344
21325
|
const sub = this.afterNodePatch.subscribe(() => {
|
21345
21326
|
this.updateTipLayoutAfterPatch();
|
21346
21327
|
sub.unsubscribe();
|
21347
21328
|
});
|
21348
|
-
return
|
21329
|
+
return scaleFitContainer;
|
21349
21330
|
}
|
21350
21331
|
};
|
21351
21332
|
}
|
@@ -21454,30 +21435,21 @@ class DocEditor {
|
|
21454
21435
|
};
|
21455
21436
|
const itemsVNode = options.map(item => {
|
21456
21437
|
const ckbVNode = {
|
21457
|
-
sel: multiSelect ? '
|
21458
|
-
data: {
|
21459
|
-
attrs: {
|
21460
|
-
type: 'checkbox',
|
21461
|
-
name: 'data-list',
|
21462
|
-
}
|
21463
|
-
}
|
21438
|
+
sel: multiSelect ? 'div.editor-list-checkbox' : 'div.editor-list-radiobox',
|
21439
|
+
data: {}
|
21464
21440
|
};
|
21465
21441
|
if (item.checked) {
|
21466
|
-
ckbVNode.
|
21442
|
+
ckbVNode.sel += '.checked';
|
21443
|
+
//ckbVNode.data.attrs['checked'] = true;
|
21467
21444
|
}
|
21468
21445
|
return {
|
21469
|
-
sel: 'div', data: {
|
21446
|
+
sel: 'div', data: { on: {
|
21447
|
+
click: () => {
|
21448
|
+
onChangeHandler(item.code);
|
21449
|
+
}
|
21450
|
+
} }, children: [ckbVNode, {
|
21470
21451
|
sel: 'label',
|
21471
|
-
data: {
|
21472
|
-
attrs: {
|
21473
|
-
//for:"data-list-"+item.code,
|
21474
|
-
},
|
21475
|
-
on: {
|
21476
|
-
click: (evt) => {
|
21477
|
-
onChangeHandler(item.code);
|
21478
|
-
}
|
21479
|
-
}
|
21480
|
-
},
|
21452
|
+
data: {},
|
21481
21453
|
text: item.value
|
21482
21454
|
}]
|
21483
21455
|
};
|
@@ -21641,7 +21613,7 @@ class DocEditor {
|
|
21641
21613
|
rule.setRuleOptions({ width: this.viewOptions.docPageSettings.width, pagePL, pagePR, docLeft });
|
21642
21614
|
}
|
21643
21615
|
version() {
|
21644
|
-
return "2.2.
|
21616
|
+
return "2.2.2";
|
21645
21617
|
}
|
21646
21618
|
switchPageHeaderEditor() {
|
21647
21619
|
this.docCtx.document.switchPageHeaderEditor(this.selectionState, null);
|
@@ -21791,6 +21763,54 @@ class DocumentCombine {
|
|
21791
21763
|
}
|
21792
21764
|
}
|
21793
21765
|
|
21766
|
+
class DocumentEvalFunc {
|
21767
|
+
docCtx;
|
21768
|
+
constructor(docCtx) {
|
21769
|
+
this.docCtx = docCtx;
|
21770
|
+
}
|
21771
|
+
scriptsFunc;
|
21772
|
+
/**
|
21773
|
+
* 实例化动态脚本
|
21774
|
+
*/
|
21775
|
+
initScripts(scripts) {
|
21776
|
+
this.destroyScripts();
|
21777
|
+
if (scripts) {
|
21778
|
+
try {
|
21779
|
+
const func = new Function("docCtx", scripts);
|
21780
|
+
this.scriptsFunc = func(this.docCtx);
|
21781
|
+
}
|
21782
|
+
catch (e) {
|
21783
|
+
console.error("自定义标本解析错误", e);
|
21784
|
+
}
|
21785
|
+
}
|
21786
|
+
// const func = (docCtx: DocumentContext) => {
|
21787
|
+
// const sexELe = docCtx.getControlById('NqoYI')
|
21788
|
+
// const dyEle = docCtx.getControlById('gTuBI');
|
21789
|
+
// return () => {
|
21790
|
+
// if (sexELe && dyEle) {
|
21791
|
+
// const sexValue = sexELe.getValue();
|
21792
|
+
// const dyValue = sexValue === '1' ? '男的吗' : sexValue === '2' ? '女的吗' : '难道是人妖吗';
|
21793
|
+
// dyEle.setValue(dyValue);
|
21794
|
+
// }
|
21795
|
+
// };
|
21796
|
+
// };
|
21797
|
+
}
|
21798
|
+
/**
|
21799
|
+
* 销毁动态脚本实例
|
21800
|
+
*/
|
21801
|
+
destroyScripts() {
|
21802
|
+
if (this.scriptsFunc) {
|
21803
|
+
this.scriptsFunc = null;
|
21804
|
+
}
|
21805
|
+
}
|
21806
|
+
/**
|
21807
|
+
* 触发动态脚本
|
21808
|
+
*/
|
21809
|
+
invokedScripts() {
|
21810
|
+
this.scriptsFunc?.();
|
21811
|
+
}
|
21812
|
+
}
|
21813
|
+
|
21794
21814
|
function createPrintTemplate({ width, height, orient }) {
|
21795
21815
|
return `
|
21796
21816
|
<!DOCTYPE html>
|