1
0
mirror of https://github.com/schollz/cowyo.git synced 2023-08-10 21:13:00 +03:00

Split inline CSS & javascript into their own files

This commit is contained in:
Daniel Heath 2018-01-31 13:34:40 +11:00
parent da6d6e5d43
commit 5e4a317b10
4 changed files with 521 additions and 466 deletions

File diff suppressed because one or more lines are too long

100
static/css/default.css Normal file
View File

@ -0,0 +1,100 @@
body.ListPage span {
cursor: pointer;
}
body {
background: #fff;
}
.success {
color: #5cb85c;
font-weight: bold;
}
.failure {
color: #d9534f;
font-weight: bold;
}
.pure-menu a {
color: #777;
}
.deleting {
opacity: 0.5;
}
#wrap {
position: absolute;
top: 50px;
left: 0px;
right: 0px;
bottom: 0px;
}
#pad {
height:100%;
}
body.EditPage {
overflow:hidden;
}
body#pad textarea {
width: 100%;
height: 100%;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
border: 0;
border: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
resize: none;
font-size: 1.0em;
font-family: 'Open Sans','Segoe UI',Tahoma,Arial,sans-serif;
}
body#pad.HasDotInName textarea {
font-family: "Lucida Console", Monaco, monospace;
}
.markdown-body ul, .markdown-body ol {
padding-left: 0em;
}
@media (min-width: 5em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 2%;
padding-right: 2%;
}
.pure-menu .pure-menu-horizontal {
max-width: 300px;
}
.pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
padding-left:1.2em;
padding-right:em;
}
}
@media (min-width: 50em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 10%;
padding-right: 10%;
}
.pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
padding: .5em 1em;
}
}
@media (min-width: 70em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 15%;
padding-right: 15%;
}
}
@media (min-width: 100em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 20%;
padding-right: 20%;
}
}

343
static/js/cowyo.js Normal file
View File

@ -0,0 +1,343 @@
"use strict";
var oulipo = false;
$(window).load(function() {
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
$('#saveEditButton').removeClass()
$('#saveEditButton').text("Editing");
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
// This will apply the debounce effect on the keyup event
// And it only fires 500ms or half a second after the user stopped typing
var prevText = $('#userInput').val();
$('#userInput').on('keyup', debounce(function() {
if (prevText == $('#userInput').val()) {
return // no changes
}
prevText = $('#userInput').val();
if (oulipo) {
$('#userInput').val($('#userInput').val().replace(/e/g,""));
}
$('#saveEditButton').removeClass()
$('#saveEditButton').text("Saving")
upload();
}, window.debounceMS));
function upload() {
$.ajax({
type: 'POST',
url: '/update',
data: JSON.stringify({
new_text: $('#userInput').val(),
page: window.cowyo.pageName,
fetched_at: window.lastFetch,
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
window.lastFetch = data.unix_time;
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function primeForSelfDestruct() {
$.ajax({
type: 'POST',
url: '/prime',
data: JSON.stringify({
page: window.cowyo.pageName,
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function lockPage(passphrase) {
$.ajax({
type: 'POST',
url: '/lock',
data: JSON.stringify({
page: window.cowyo.pageName,
passphrase: passphrase
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.success == true && $('#lockPage').text() == "Lock") {
window.location = "/" + window.cowyo.pageName + "/view";
}
if (data.success == true && $('#lockPage').text() == "Unlock") {
window.location = "/" + window.cowyo.pageName + "/edit";
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function publishPage() {
$.ajax({
type: 'POST',
url: '/publish',
data: JSON.stringify({
page: window.cowyo.pageName,
publish: $('#publishPage').text() == "Publish"
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.message == "Unpublished") {
$('#publishPage').text("Publish");
} else {
$('#publishPage').text("Unpublish");
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function encryptPage(passphrase) {
$.ajax({
type: 'POST',
url: '/encrypt',
data: JSON.stringify({
page: window.cowyo.pageName,
passphrase: passphrase
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.success == true && $('#encryptPage').text() == "Encrypt") {
window.location = "/" + window.cowyo.pageName + "/view";
}
if (data.success == true && $('#encryptPage').text() == "Decrypt") {
window.location = "/" + window.cowyo.pageName + "/edit";
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function clearOld() {
$.ajax({
type: 'DELETE',
url: '/oldlist',
data: JSON.stringify({
page: window.cowyo.pageName
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.success == true) {
window.location = "/" + window.cowyo.pageName + "/list";
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass();
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
$("#encryptPage").click(function(e) {
e.preventDefault();
var passphrase = prompt("Please enter a passphrase. Note: Encrypting will remove all previous history.", "");
if (passphrase != null) {
encryptPage(passphrase);
}
});
$("#erasePage").click(function(e) {
e.preventDefault();
var r = confirm("Are you sure you want to erase?");
if (r == true) {
window.location = "/" + window.cowyo.pageName + "/erase";
} else {
x = "You pressed Cancel!";
}
});
$("#selfDestructPage").click(function(e) {
e.preventDefault();
var r = confirm("This will erase the page the next time it is opened, are you sure you want to do that?");
if (r == true) {
primeForSelfDestruct();
} else {
x = "You pressed Cancel!";
}
});
$("#lockPage").click(function(e) {
e.preventDefault();
var passphrase = prompt("Please enter a passphrase to lock", "");
if (passphrase != null) {
if ($('#lockPage').text() == "Lock") {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Locking");
} else {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Unlocking");
}
lockPage(passphrase);
// POST encrypt page
// reload page
}
});
$("#publishPage").click(function(e) {
e.preventDefault();
var message = " This will add your page to the sitemap.xml so it will be indexed by search engines.";
if ($('#publishPage').text() == "Unpublish") {
message = "";
}
var confirmed = confirm("Are you sure?" + message);
if (confirmed == true) {
if ($('#publishPage').text() == "Unpublish") {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Unpublishing");
} else {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Publishing");
}
publishPage();
}
});
$("#clearOld").click(function(e) {
e.preventDefault();
var r = confirm("This will erase all cleared list items, are you sure you want to do that? (Versions will stay in history).");
if (r == true) {
clearOld()
} else {
x = "You pressed Cancel!";
}
});
$("textarea").keydown(function(e) {
if(e.keyCode === 9) { // tab was pressed
// get caret position/selection
var start = this.selectionStart;
var end = this.selectionEnd;
var $this = $(this);
var value = $this.val();
// set textarea value to: text before caret + tab + text after caret
$this.val(value.substring(0, start)
+ "\t"
+ value.substring(end));
// put caret at right position again (add one for the tab)
this.selectionStart = this.selectionEnd = start + 1;
// prevent the focus lose
e.preventDefault();
}
});
$('.deletable').click(function(event) {
event.preventDefault();
var lineNum = $(this).attr('id');
$.ajax({
url: "/listitem" + '?' + $.param({
"lineNum": lineNum,
"page": window.cowyo.pageName
}),
type: 'DELETE',
success: function() {
window.location.reload(true);
}
});
event.target.classList.add('deleting');
});
});

View File

@ -1,6 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -22,474 +21,41 @@
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png"> <meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
<meta name="theme-color" content="#fff"> <meta name="theme-color" content="#fff">
{{ if and .CustomCSS .ReadPage }} {{ if and .CustomCSS .ReadPage }}
<link rel="stylesheet" type="text/css" href="/static/css/custom.css"> <link rel="stylesheet" type="text/css" href="/static/css/custom.css">
{{ else }} {{ else }}
<script type="text/javascript" src="/static/js/jquery-1.8.3.js"></script> <link rel="stylesheet" type="text/css" href="/static/css/github-markdown.css">
<link rel="stylesheet" type="text/css" href="/static/css/github-markdown.css"> <link rel="stylesheet" type="text/css" href="/static/css/menus-min.css">
<link rel="stylesheet" type="text/css" href="/static/css/menus-min.css"> <link rel="stylesheet" type="text/css" href="/static/css/base-min.css">
<link rel="stylesheet" type="text/css" href="/static/css/base-min.css"> <link rel="stylesheet" href="/static/css/highlight.css">
<link rel="stylesheet" href="/static/css/highlight.css"> <link rel="stylesheet" href="/static/css/default.css">
<script src="/static/js/highlight.min.js"></script> <script type="text/javascript" src="/static/js/jquery-1.8.3.js"></script>
<script type="text/javascript" src="/static/js/highlight.pack.js"></script> <script src="/static/js/highlight.min.js"></script>
<script type="text/javascript" src="/static/js/highlight.pack.js"></script>
{{ end }}
<style type="text/css">
{{ if .ListPage }}
/* Required for lists */
span { cursor: pointer; }
{{ end }}
body {
background: #fff;
}
.success {
color: #5cb85c;
font-weight: bold;
}
.failure {
color: #d9534f;
font-weight: bold;
}
.pure-menu a {
color: #777;
}
.deleting {
opacity: 0.5;
}
#wrap {
position: absolute;
top: 50px;
left: 0px;
right: 0px;
bottom: 0px;
}
#pad {
height:100%;
}
{{ if .EditPage }}
body {
overflow:hidden;
}
{{ end }}
body#pad textarea {
width: 100%;
height: 100%;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
border: 0;
border: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
resize: none;
font-size: 1.0em;
{{ if .HasDotInName }}
font-family: "Lucida Console", Monaco, monospace;
{{else}}
font-family: 'Open Sans','Segoe UI',Tahoma,Arial,sans-serif;
{{ end }}
}
.markdown-body ul, .markdown-body ol {
padding-left: 0em;
}
@media (min-width: 5em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 2%;
padding-right: 2%;
}
.pure-menu .pure-menu-horizontal {
max-width: 300px;
}
.pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
padding-left:1.2em;
padding-right:em;
}
}
@media (min-width: 50em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 10%;
padding-right: 10%;
}
.pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
padding: .5em 1em;
}
}
@media (min-width: 70em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 15%;
padding-right: 15%;
}
}
@media (min-width: 100em) {
div#menu, div#rendered, body#pad textarea {
padding-left: 20%;
padding-right: 20%;
}
}
</style>
{{ end }}
<title>{{ .Page }}</title> <title>{{ .Page }}</title>
<script type='text/javascript'> <script type='text/javascript'>
oulipo = false; hljs.initHighlightingOnLoad();
//<![CDATA[ window.cowyo = {
$(window).load(function() { debounceMS: {{ .Debounce }},
// Returns a function, that, as long as it continues to be invoked, will not lastFetch: {{ .UnixTime }},
// be triggered. The function will be called after it stops being called for pageName: "{{ .Page }}",
// N milliseconds. If `immediate` is passed, trigger the function on the }
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
$('#saveEditButton').removeClass()
$('#saveEditButton').text("Editing");
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
// This will apply the debounce effect on the keyup event
// And it only fires 500ms or half a second after the user stopped typing
var prevText = $('#userInput').val();
$('#userInput').on('keyup', debounce(function() {
if (prevText == $('#userInput').val()) {
return // no changes
}
prevText = $('#userInput').val();
if (oulipo) {
$('#userInput').val($('#userInput').val().replace(/e/g,""));
}
$('#saveEditButton').removeClass()
$('#saveEditButton').text("Saving")
upload();
}, {{ .Debounce }}));
var lastFetch = {{ .UnixTime }};
function upload() {
$.ajax({
type: 'POST',
url: '/update',
data: JSON.stringify({
new_text: $('#userInput').val(),
page: "{{ .Page }}",
fetched_at: lastFetch,
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
lastFetch = data.unix_time;
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function primeForSelfDestruct() {
$.ajax({
type: 'POST',
url: '/prime',
data: JSON.stringify({
page: "{{ .Page }}"
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function lockPage(passphrase) {
$.ajax({
type: 'POST',
url: '/lock',
data: JSON.stringify({
page: "{{ .Page }}",
passphrase: passphrase
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.success == true && $('#lockPage').text() == "Lock") {
window.location = "/{{ .Page }}/view";
}
if (data.success == true && $('#lockPage').text() == "Unlock") {
window.location = "/{{ .Page }}/edit";
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function publishPage() {
$.ajax({
type: 'POST',
url: '/publish',
data: JSON.stringify({
page: "{{ .Page }}",
publish: $('#publishPage').text() == "Publish"
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.message == "Unpublished") {
$('#publishPage').text("Publish");
} else {
$('#publishPage').text("Unpublish");
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function encryptPage(passphrase) {
$.ajax({
type: 'POST',
url: '/encrypt',
data: JSON.stringify({
page: "{{ .Page }}",
passphrase: passphrase
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.success == true && $('#encryptPage').text() == "Encrypt") {
window.location = "/{{ .Page }}/view";
}
if (data.success == true && $('#encryptPage').text() == "Decrypt") {
window.location = "/{{ .Page }}/edit";
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass()
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
function clearOld() {
$.ajax({
type: 'DELETE',
url: '/oldlist',
data: JSON.stringify({
page: "{{ .Page }}"
}),
success: function(data) {
$('#saveEditButton').removeClass()
if (data.success == true) {
$('#saveEditButton').addClass("success");
} else {
$('#saveEditButton').addClass("failure");
}
$('#saveEditButton').text(data.message);
if (data.success == true) {
window.location = "/{{ .Page }}/list";
}
},
error: function(xhr, error) {
$('#saveEditButton').removeClass();
$('#saveEditButton').addClass("failure");
$('#saveEditButton').text(error);
},
contentType: "application/json",
dataType: 'json'
});
}
$("#encryptPage").click(function(e) {
e.preventDefault();
var passphrase = prompt("Please enter a passphrase. Note: Encrypting will remove all previous history.", "");
if (passphrase != null) {
encryptPage(passphrase);
}
});
$("#erasePage").click(function(e) {
e.preventDefault();
var r = confirm("Are you sure you want to erase?");
if (r == true) {
window.location = "/{{ .Page }}/erase";
} else {
x = "You pressed Cancel!";
}
});
$("#selfDestructPage").click(function(e) {
e.preventDefault();
var r = confirm("This will erase the page the next time it is opened, are you sure you want to do that?");
if (r == true) {
primeForSelfDestruct();
} else {
x = "You pressed Cancel!";
}
});
$("#lockPage").click(function(e) {
e.preventDefault();
var passphrase = prompt("Please enter a passphrase to lock", "");
if (passphrase != null) {
if ($('#lockPage').text() == "Lock") {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Locking");
} else {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Unlocking");
}
lockPage(passphrase);
// POST encrypt page
// reload page
}
});
$("#publishPage").click(function(e) {
e.preventDefault();
var message = " This will add your page to the sitemap.xml so it will be indexed by search engines.";
if ($('#publishPage').text() == "Unpublish") {
message = "";
}
var confirmed = confirm("Are you sure?" + message);
if (confirmed == true) {
if ($('#publishPage').text() == "Unpublish") {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Unpublishing");
} else {
$('#saveEditButton').removeClass();
$("#saveEditButton").text("Publishing");
}
publishPage();
}
});
$("#clearOld").click(function(e) {
e.preventDefault();
var r = confirm("This will erase all cleared list items, are you sure you want to do that? (Versions will stay in history).");
if (r == true) {
clearOld()
} else {
x = "You pressed Cancel!";
}
});
$("textarea").keydown(function(e) {
if(e.keyCode === 9) { // tab was pressed
// get caret position/selection
var start = this.selectionStart;
var end = this.selectionEnd;
var $this = $(this);
var value = $this.val();
// set textarea value to: text before caret + tab + text after caret
$this.val(value.substring(0, start)
+ "\t"
+ value.substring(end));
// put caret at right position again (add one for the tab)
this.selectionStart = this.selectionEnd = start + 1;
// prevent the focus lose
e.preventDefault();
}
});
$('.deletable').click(function(event) {
event.preventDefault();
var lineNum = $(this).attr('id');
$.ajax({
url: "/listitem" + '?' + $.param({
"lineNum": lineNum,
"page": "{{ .Page }}"
}),
type: 'DELETE',
success: function() {
window.location.reload(true);
}
});
event.target.classList.add('deleting');
});
}); //]]>
</script> </script>
<script>hljs.initHighlightingOnLoad();</script> <script type="text/javascript" src="/static/js/cowyo.js"></script>
</head> </head>
<body id="pad"> <body id="pad" class="
{{ if .EditPage }} EditPage {{ end }}
{{ if .ViewPage }} ViewPage {{ end }}
{{ if .ListPage }} ListPage {{ end }}
{{ if .HistoryPage }} HistoryPage {{ end }}
{{ if .ReadPage }} ReadPage {{ end }}
{{ if .DontKnowPage }} DontKnowPage {{ end }}
{{ if .DirectoryPage }} DirectoryPage {{ end }}
{{ if .HasDotInName }} HasDotInName {{ end }}
">
<article class="markdown-body"> <article class="markdown-body">
{{ if .ReadPage }} {{ if .ReadPage }}
@ -629,7 +195,7 @@ body#pad textarea {
</tr> </tr>
{{ end }} {{ end }}
</table> </table>
{{end}} {{ end }}
</div> </div>
</div> </div>
</article> </article>