Initial version

pull/2748/head
Alex Dima 9 years ago
parent 7ac7ab93e8
commit 6dcc1c017d

1
.gitignore vendored

@ -0,0 +1 @@
/node_modules/

@ -0,0 +1,2 @@
/.gitignore
/.vscode/

@ -0,0 +1,6 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.trimTrailingWhitespace": true,
"editor.tabSize": 4,
"editor.insertSpaces": false
}

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -1,2 +1,27 @@
# monaco-editor-samples
Samples for using the Monaco Editor
# Monaco Editor Samples
Standalone HTML samples showing how to integrate the Monaco Editor.
## Running
From npm:
```
npm install monaco-editor-samples
cd node_modules/monaco-editor-samples
npm run simpleserver
```
From source:
```
git clone https://github.com/Microsoft/monaco-editor-samples.git
cd monaco-editor-samples
npm install .
npm run simpleserver
```
Go to <a href="http://localhost:8888">localhost:8888</a> and explore the samples!
## License
MIT

@ -0,0 +1,15 @@
{
"name": "monaco-editor-samples",
"version": "0.0.1",
"description": "Samples for using the Monaco Editor",
"main": "index.js",
"scripts": {
"simpleserver": "node_modules/.bin/http-server -c-1 ./ -p 8888"
},
"author": "Microsoft Corporation",
"license": "MIT",
"dependencies": {
"http-server": "^0.9.0",
"monaco-editor": "0.x.x"
}
}

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
</head>
<body>
<h2>Monaco Diff Editor Sample</h2>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }});
require(['vs/editor/editor.main'], function() {
var diffEditor = monaco.editor.createDiffEditor(document.getElementById('container'));
monaco.Promise.join([xhr('original.txt'), xhr('modified.txt')]).then(function(r) {
var originalTxt = r[0].responseText;
var modifiedTxt = r[1].responseText;
diffEditor.setModel({
original: monaco.editor.createModel(originalTxt, 'javascript'),
modified: monaco.editor.createModel(modifiedTxt, 'javascript'),
})
});
});
</script>
<script>
function xhr(url) {
var req = null;
return new monaco.Promise(function(c,e,p) {
req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req._canceled) { return; }
if (req.readyState === 4) {
if ((req.status >= 200 && req.status < 300) || req.status === 1223) {
c(req);
} else {
e(req);
}
req.onreadystatechange = function () { };
} else {
p(req);
}
};
req.open("GET", url, true );
req.responseType = "";
req.send(null);
}, function () {
req._canceled = true;
req.abort();
});
}
</script>
</body>
</html>

@ -0,0 +1,74 @@
/// <reference path="../../references.js" />
(function () {
"use strict";
var deltaDecorations = function (oldDecorations, newDecorations) {
/// <summary>
/// Update oldDecorations to match newDecorations.
/// It will remove old decorations which are not found in new decorations
/// and add only the really new decorations.
/// </summary>
/// <param name="oldDecorations" type="Array">
/// An array containing ids of existing decorations
/// </param>
/// <param name="newDecorations" type="Array">
/// An array containing literal objects describing new decorations. A
/// literal contains the following two fields:
/// range
/// options
/// </param>
/// <returns type="Array">
/// Returns an array of decorations ids
/// </returns>
var hashFunc = function (range, options) {
return range.startLineNumber + "," + range.startColumn + "-" + range.endLineNumber + "," + range.endColumn +
"-" + options.hoverMessage + "-" + options.className + "-" + options.isOverlay + "-" + options.showInOverviewRuler;
};
return this.changeDecorations(function (changeAccessor) {
var i, len, oldDecorationsMap = {}, hash;
// Record old decorations in a map
// Two decorations can have the same hash
for (i = 0, len = oldDecorations.length; i < len; i++) {
hash = hashFunc(this.getDecorationRange(oldDecorations[i]), this.getDecorationOptions(oldDecorations[i]));
oldDecorationsMap[hash] = oldDecorationsMap[hash] || [];
oldDecorationsMap[hash].push(oldDecorations[i]);
}
// Add only new decorations & mark reused ones
var j, lenJ, result = [], usedOldDecorations = {}, oldDecorationsCandidates, reusedOldDecoration;
for (i = 0, len = newDecorations.length; i < len; i++) {
hash = hashFunc(newDecorations[i].range, newDecorations[i].options);
reusedOldDecoration = false;
if (oldDecorationsMap.hasOwnProperty(hash)) {
oldDecorationsCandidates = oldDecorationsMap[hash];
// We can try reusing an old decoration (if it hasn't been reused before)
for (j = 0, lenJ = oldDecorationsCandidates.length; j < lenJ; j++) {
if (!usedOldDecorations.hasOwnProperty(oldDecorationsCandidates[j])) {
// Found an old decoration which can be reused & it hasn't been reused before
reusedOldDecoration = true;
usedOldDecorations[oldDecorationsCandidates[j]] = true;
result.push(oldDecorationsCandidates[j]);
break;
}
}
}
if (!reusedOldDecoration) {
result.push(changeAccessor.addDecoration(newDecorations[i].range, newDecorations[i].options));
}
}
// Remove unused old decorations
for (i = 0, len = oldDecorations.length; i < len; i++) {
if (!usedOldDecorations.hasOwnProperty(oldDecorations[i])) {
changeAccessor.removeDecoration(oldDecorations[i]);
}
}
return result;
}.bind(this));
};
})();

@ -0,0 +1,61 @@
/// <reference path="../../references.js" />
(function () {
"use strict";
// Some useless comment
var deltaDecorations = function (oldDecorations, newDecorations) {
/// <summary>
/// Update oldDecorations to match newDecorations.
/// It will remove old decorations which are not found in new decorations
/// and add only the really new decorations.
/// </summary>
/// <param name="oldDecorations" type="Array">
/// An array containing ids of existing decorations
/// </param>
/// <param name="newDecorations" type="Array">
/// An array containing literal objects describing new decorations. A
/// literal contains the following two fields:
/// range
/// options
/// </param>
/// <returns type="Array">
/// Returns an array of decorations ids
/// </returns>
var hashFunc = function (range, options) {
return range.startLineNumber + "," + range.startColumn + "-" + range.endLineNumber + "," + range.endColumn +
"-" + options.hoverMessage + "-" + options.className + "-" + options.isOverlay + "-" + options.showInOverviewRuler;
};
return this.changeDecorations(function (changeAccessor) {
var i, len, oldDecorationsMap = {}, hash;
// Record old decorations in a map
for (i = 0, len = oldDecorations.length; i < len; i++) {
hash = hashFunc(this.getDecorationRange(oldDecorations[i]), this.getDecorationOptions(oldDecorations[i]));
oldDecorationsMap[hash] = i;
}
// Add only new decorations & mark reused ones
var result = [], usedOldDecorationsMap = {};
for (i = 0, len = newDecorations.length; i < len; i++) {
hash = hashFunc(newDecorations[i].range, newDecorations[i].options);
if (oldDecorationsMap.hasOwnProperty(hash)) {
usedOldDecorationsMap[oldDecorationsMap[hash]] = true;
result.push(oldDecorations[oldDecorationsMap[hash]]);
} else {
result.push(changeAccessor.addDecoration(newDecorations[i].range, newDecorations[i].options));
}
}
// Remove unused old decorations
for (i = 0, len = oldDecorations.length; i < len; i++) {
if (!usedOldDecorationsMap.hasOwnProperty(i)) {
changeAccessor.removeDecoration(oldDecorations[i]);
}
}
return result;
}.bind(this));
};
})();

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
</head>
<body>
<h2>Monaco Editor Sample</h2>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }});
require(['vs/editor/editor.main'], function() {
var editor = monaco.editor.create(document.getElementById('container'), {
value: [
'function x() {',
'\tconsole.log("Hello world!");',
'}'
].join('\n'),
language: 'javascript'
});
});
</script>
</body>
</html>

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<title>Editor in tiny iframe</title>
<style>
#myIframe1 {
border: 1px solid blue;
width: 0;
height: 0;
}
#myIframe2 {
border: 1px solid red;
display: none;
}
#myIframe3 {
border: 1px solid green;
visibility: hidden;
}
#programmaticIframe {
border: 1px solid yellow;
}
</style>
</head>
<body>
<h2>Monaco Editor inside iframes sample</h2>
<br/>
<br/> 0x0:
<br/>
<iframe id="myIframe1" src="inner.html"></iframe>
display:none:
<br/>
<iframe id="myIframe2" src="inner.html"></iframe>
visibility:hidden:
<br/>
<iframe id="myIframe3" src="inner.html"></iframe>
taken off-dom while loading:
<br/>
<script>
var myIframe1 = document.getElementById('myIframe1');
var myIframe2 = document.getElementById('myIframe2');
var myIframe3 = document.getElementById('myIframe3');
var programmaticIframe = document.createElement('iframe');
programmaticIframe.id = 'programmaticIframe';
programmaticIframe.src = "inner.html";
// trigger its loading & take it off dom
document.body.appendChild(programmaticIframe);
setTimeout(function() {
document.body.removeChild(programmaticIframe);
}, 10);
setTimeout(function() {
document.body.appendChild(programmaticIframe);
[
myIframe1,
myIframe2,
myIframe3,
programmaticIframe
].forEach(reveal);
}, 3000);
function reveal(iframe) {
iframe.style.width = '400px';
iframe.style.height = '100px';
iframe.style.display = 'block';
iframe.style.visibility = 'visible';
}
</script>
</body>
</html>

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<style type="text/css">
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container" style="width:100%;height:100%"></div>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }});
require(['vs/editor/editor.main'], function() {
var editor = monaco.editor.create(document.getElementById('container'), {
value: [
'function x() {',
'\tconsole.log("Hello world!");',
'}'
].join('\n'),
language: 'javascript'
});
window.onresize = function() {
editor.layout();
};
});
</script>
</body>
</html>

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
</head>
<body>
<h2>Monaco Editor Localization Sample</h2>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }});
require.config({
'vs/nls' : {
availableLanguages: {
'*': 'de'
}
}
});
require(['vs/editor/editor.main'], function() {
var editor = monaco.editor.create(document.getElementById('container'), {
value: [
'function x() {',
'\tconsole.log("Hello world!");',
'}'
].join('\n'),
language: 'javascript'
});
});
</script>
</body>
</html>

@ -0,0 +1,111 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<style>
.monaco-editor.vs .token.custom-info {
color: grey;
}
.monaco-editor.vs .token.custom-error {
color: red;
font-weight: bold;
font-size: 1.2em;
}
.monaco-editor.vs .token.custom-notice {
color: orange;
}
.monaco-editor.vs .token.custom-date {
color: green;
}
</style>
</head>
<body>
<h2>Monarch Tokenizer Sample</h2>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }});
require(['vs/editor/editor.main'], function() {
monaco.languages.register({
id: 'myCustomLanguage'
});
monaco.languages.setMonarchTokensProvider('myCustomLanguage', {
tokenizer: {
root: [
[/\[error.*/, "custom-error"],
[/\[notice.*/, "custom-notice"],
[/\[info.*/, "custom-info"],
[/\[[a-zA-Z 0-9:]+\]/, "custom-date"],
],
}
});
var editor = monaco.editor.create(document.getElementById('container'), {
value: getCode(),
language: 'myCustomLanguage'
});
});
function getCode() {
return [
'[Sun Mar 7 16:02:00 2004] [notice] Apache/1.3.29 (Unix) configured -- resuming normal operations',
'[Sun Mar 7 16:02:00 2004] [info] Server built: Feb 27 2004 13:56:37',
'[Sun Mar 7 16:02:00 2004] [notice] Accept mutex: sysvsem (Default: sysvsem)',
'[Sun Mar 7 16:05:49 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 16:45:56 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 17:13:50 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 17:21:44 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 17:23:53 2004] statistics: Use of uninitialized value in concatenation (.) or string at /home/httpd/twiki/lib/TWiki.pm line 528.',
'[Sun Mar 7 17:23:53 2004] statistics: Can\'t create file /home/httpd/twiki/data/Main/WebStatistics.txt - Permission denied',
'[Sun Mar 7 17:27:37 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 17:31:39 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 17:58:00 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 18:00:09 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 18:10:09 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 18:19:01 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 18:42:29 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 18:52:30 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 18:58:52 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 19:03:58 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 19:08:55 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 20:04:35 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 20:11:33 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 20:12:55 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 20:25:31 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 20:44:48 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome',
'[Sun Mar 7 21:20:14 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 21:31:12 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 21:39:55 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Sun Mar 7 21:44:10 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 01:35:13 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 01:47:06 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 01:59:13 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 02:12:24 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 02:54:54 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 03:46:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 03:48:18 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 03:52:17 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 03:55:09 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 04:22:55 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 04:24:47 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 04:40:32 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 04:55:40 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 04:59:13 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 05:22:57 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 05:24:29 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'[Mon Mar 8 05:31:47 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed',
'<11>httpd[31628]: [error] [client xx.xx.xx.xx] File does not exist: /usr/local/installed/apache/htdocs/squirrelmail/_vti_inf.html in 29-Mar 15:18:20.50 from xx.xx.xx.xx',
'<11>httpd[25859]: [error] [client xx.xx.xx.xx] File does not exist: /usr/local/installed/apache/htdocs/squirrelmail/_vti_bin/shtml.exe/_vti_rpc in 29-Mar 15:18:20.54 from xx.xx.xx.xx',
].join('\n');;
}
</script>
</body>
</html>

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
</head>
<body>
<h2>Monaco Editor Shared Models Sample</h2>
<div id="container1" style="width:400px;height:200px;border:1px solid grey"></div>
<div id="container2" style="width:400px;height:200px;border:1px solid grey"></div>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' }});
require(['vs/editor/editor.main'], function() {
var model = monaco.editor.createModel([
'function x() {',
'\tconsole.log("Hello world!");',
'}'
].join('\n'),
'javascript'
);
var editor1 = monaco.editor.create(document.getElementById('container1'), {
model: model
});
var editor2 = monaco.editor.create(document.getElementById('container2'), {
model: model
});
});
</script>
</body>
</html>

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<link rel="stylesheet" data-name="vs/editor/editor.main" href="../node_modules/monaco-editor/min/vs/editor/editor.main.css">
</head>
<body>
<h2>Monaco Editor Sync Loading Sample</h2>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<script>var require = { paths: { 'vs': '../node_modules/monaco-editor/min/vs' } };</script>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script src="../node_modules/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="../node_modules/monaco-editor/min/vs/editor/editor.main.js"></script>
<script>
var editor = monaco.editor.create(document.getElementById('container'), {
value: [
'function x() {',
'\tconsole.log("Hello world!");',
'}'
].join('\n'),
language: 'javascript'
});
</script>
</body>
</html>
Loading…
Cancel
Save