Add what you see is what you get markdown using pell
This commit is contained in:
parent
0c35db92ba
commit
5ffa65212c
|
@ -9,8 +9,6 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
|
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
|
||||||
<script type="text/javascript" src="/static/js/hash-wasm.js"></script>
|
<script type="text/javascript" src="/static/js/hash-wasm.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/crypto-js.js"></script>
|
<script type="text/javascript" src="/static/js/crypto-js.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/marked.js"></script>
|
|
||||||
<script type="text/javascript" src="/static/js/purify.js"></script>
|
|
||||||
<link rel="icon" href="/static/svg/favicon.svg">
|
<link rel="icon" href="/static/svg/favicon.svg">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -23,7 +21,6 @@
|
||||||
<div class="modernToolbar">
|
<div class="modernToolbar">
|
||||||
<button class="usernameBox hidden" onclick="handleGesture()" id="backButton"><div class="vcenter"><img alt="Back arrow" src="/static/svg/arrow-back.svg"></div></button>
|
<button class="usernameBox hidden" onclick="handleGesture()" id="backButton"><div class="vcenter"><img alt="Back arrow" src="/static/svg/arrow-back.svg"></div></button>
|
||||||
<button class="count" id="wordCountBox">0 words</button>
|
<button class="count" id="wordCountBox">0 words</button>
|
||||||
<button onclick="toggleMarkdown()" class="usernameBox"><div class="vcenter"><img alt="Enable markdown" src="/static/svg/markdown.svg"></div></button>
|
|
||||||
<button id="usernameBox" class="usernameBox"><div class="vcenter"><img alt="Account settings" src="/static/svg/acct-settings.svg"></div></button>
|
<button id="usernameBox" class="usernameBox"><div class="vcenter"><img alt="Account settings" src="/static/svg/acct-settings.svg"></div></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,17 +87,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="noteBox" id="noteBoxDiv">
|
<div class="noteBox" id="noteBoxDiv">
|
||||||
<textarea id="noteBox" class="noteBoxText"></textarea>
|
<div id="noteBox" class="noteBoxText"></div>
|
||||||
<iframe id="markdown" style="display: none;" sandbox=""></iframe>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript" src="/static/js/main.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
for (let i = 0; i < 40; i++) {
|
for (let i = 0; i < 40; i++) {
|
||||||
notesDiv.appendChild(loadingStuff.cloneNode())
|
notesDiv.appendChild(loadingStuff.cloneNode())
|
||||||
}
|
}
|
||||||
loadingStuff.remove()
|
loadingStuff.remove()
|
||||||
</script>
|
</script>
|
||||||
|
<script src="https://unpkg.com/pell"></script>
|
||||||
|
<script type="text/javascript" src="/static/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -385,21 +385,17 @@ body {
|
||||||
width: calc(100% - 200px);
|
width: calc(100% - 200px);
|
||||||
height: calc(100% - 50px - 30px);
|
height: calc(100% - 50px - 30px);
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
outline: none;
|
outline: none;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noteBox.mobile {
|
.noteBox.mobile {
|
||||||
flex-direction: column-reverse;
|
|
||||||
margin: 15px 0 0;
|
margin: 15px 0 0;
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.noteBoxText {
|
.noteBoxText {
|
||||||
resize: none;
|
|
||||||
background-color: var(--editor);
|
background-color: var(--editor);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -407,13 +403,29 @@ body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe#markdown {
|
.pell-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: calc(100% - 20px);
|
||||||
border: none;
|
overflow-y: scroll;
|
||||||
border-left: solid var(--bar) 1px;
|
}
|
||||||
|
|
||||||
|
.pell-button {
|
||||||
|
background-color: var(--button);
|
||||||
|
border: 1px var(--border-color) solid;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: 1px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pell-actionbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noteBox:focus {
|
.noteBox:focus {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
|
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Beautified version of crypto-js, to maintain compatibility with uMatrix
|
* Beautified version of:
|
||||||
Beautified from https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
|
* crypto-js (https://www.npmjs.com/package/crypto-js)
|
||||||
|
* (c) Crypto-JS
|
||||||
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
! function(t, e) {
|
! function(t, e) {
|
||||||
|
|
|
@ -26,11 +26,11 @@ function showElements(yesorno) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeButton.addEventListener("click", (event) => {
|
changeButton.addEventListener("click", () => {
|
||||||
async function doStuff() {
|
async function doStuff() {
|
||||||
let remote = homeserverBox.value
|
let remote = homeserverBox.value
|
||||||
|
|
||||||
if (remote == "") {
|
if (remote === "") {
|
||||||
statusBox.innerText = "A homeserver is required!"
|
statusBox.innerText = "A homeserver is required!"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,16 @@ changeButton.addEventListener("click", (event) => {
|
||||||
showElements(false)
|
showElements(false)
|
||||||
statusBox.innerText = "Connecting to homeserver..."
|
statusBox.innerText = "Connecting to homeserver..."
|
||||||
|
|
||||||
fetch(remote + "/api/version")
|
fetch(remote + "/api/versionjson")
|
||||||
.then((response) => response)
|
.then((response) => response)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
async function doStuff() {
|
async function doStuff() {
|
||||||
if (response.status == 200) {
|
if (response.status === 200) {
|
||||||
|
let version = await response.json()
|
||||||
|
let fetchClientVersion = await (await fetch("/static/version.txt")).text()
|
||||||
|
if (parseInt(version["versionnum"]) < parseInt(fetchClientVersion)) {
|
||||||
|
localStorage.setItem("legacy", "true")
|
||||||
|
}
|
||||||
localStorage.setItem("homeserverURL", remote)
|
localStorage.setItem("homeserverURL", remote)
|
||||||
|
|
||||||
if (document.referrer !== "") {
|
if (document.referrer !== "") {
|
||||||
|
@ -51,12 +56,44 @@ changeButton.addEventListener("click", (event) => {
|
||||||
else {
|
else {
|
||||||
window.location.href = "/login";
|
window.location.href = "/login";
|
||||||
}
|
}
|
||||||
|
} else if (response.status === 404) {
|
||||||
|
let legacyHomeserverCheck = await fetch(remote + "/api/version")
|
||||||
|
if (legacyHomeserverCheck.status === 200) {
|
||||||
|
let homeserverText = await legacyHomeserverCheck.text()
|
||||||
|
let homeserverFloat = homeserverText.split(" ")[2]
|
||||||
|
let homeserverNameCheck = homeserverText.split(" ")[0]
|
||||||
|
if (homeserverNameCheck !== "Burgernotes") {
|
||||||
|
statusBox.innerText = "This homeserver is not compatible with Burgernotes!"
|
||||||
|
showElements(true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
else if (response.status == 404) {
|
let homeserverInt = parseFloat(homeserverFloat) * 100
|
||||||
statusBox.innerText = "Not a valid homeserver!"
|
if (homeserverInt < 200) {
|
||||||
|
localStorage.setItem("legacy", "true")
|
||||||
|
localStorage.setItem("homeserverURL", remote)
|
||||||
|
if (document.referrer !== "") {
|
||||||
|
window.location.href = document.referrer;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
statusBox.innerText = "Something went wrong!"
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
} else if (homeserverInt > 200) {
|
||||||
|
localStorage.setItem("legacy", "false")
|
||||||
|
localStorage.setItem("homeserverURL", remote)
|
||||||
|
if (document.referrer !== "") {
|
||||||
|
window.location.href = document.referrer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statusBox.innerText = "This homeserver is not compatible with Burgernotes!"
|
||||||
|
showElements(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
statusBox.innerText = "This homeserver is not compatible with Burgernotes!"
|
||||||
showElements(true)
|
showElements(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +103,7 @@ changeButton.addEventListener("click", (event) => {
|
||||||
doStuff()
|
doStuff()
|
||||||
});
|
});
|
||||||
|
|
||||||
backButton.addEventListener("click", (event) => {
|
backButton.addEventListener("click", () => {
|
||||||
history.back()
|
history.back()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@ let inputNameBox = document.getElementById("inputNameBox")
|
||||||
let backButton = document.getElementById("backButton")
|
let backButton = document.getElementById("backButton")
|
||||||
let opButton = document.getElementById("opButton")
|
let opButton = document.getElementById("opButton")
|
||||||
|
|
||||||
async function loginFetch(username, password) {
|
async function loginFetch(username, password, changePass, newPass) {
|
||||||
|
if (localStorage.getItem("legacy") !== true) {
|
||||||
return await fetch(remote + "/api/login", {
|
return await fetch(remote + "/api/login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -32,6 +33,28 @@ async function loginFetch(username, password) {
|
||||||
"X-Burgernotes-Version": "200"
|
"X-Burgernotes-Version": "200"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
let passwordChange, newPassChecked
|
||||||
|
if (changePass) {
|
||||||
|
passwordChange = "yes"
|
||||||
|
newPassChecked = newPass
|
||||||
|
} else {
|
||||||
|
passwordChange = "no"
|
||||||
|
newPassChecked = password
|
||||||
|
}
|
||||||
|
return await fetch(remote + "/api/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
passwordchange: passwordChange,
|
||||||
|
newpass: newPassChecked
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=UTF-8",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addLegacyPassword(secretKey, password) {
|
async function addLegacyPassword(secretKey, password) {
|
||||||
|
@ -165,11 +188,12 @@ signupButton.addEventListener("click", () => {
|
||||||
showElements(true)
|
showElements(true)
|
||||||
statusBox.innerText = "Signing in..."
|
statusBox.innerText = "Signing in..."
|
||||||
|
|
||||||
const login = await loginFetch(username, await hashpass(password))
|
const hashedPass = await hashpass(password)
|
||||||
|
const login = await loginFetch(username, hashedPass, false, "")
|
||||||
const loginData = await login.json()
|
const loginData = await login.json()
|
||||||
if (login.status === 401) {
|
if (login.status === 401) {
|
||||||
// Trying hashpassold
|
// Trying hashpassold
|
||||||
const loginOld = await loginFetch(username, await hashpassold(password))
|
const loginOld = await loginFetch(username, await hashpassold(password), true, hashedPass)
|
||||||
const loginDataOld = await loginOld.json()
|
const loginDataOld = await loginOld.json()
|
||||||
if (loginOld.status === 401) {
|
if (loginOld.status === 401) {
|
||||||
statusBox.innerText = "Username or password incorrect!"
|
statusBox.innerText = "Username or password incorrect!"
|
||||||
|
@ -181,7 +205,7 @@ signupButton.addEventListener("click", () => {
|
||||||
if (loginDataOld["legacyPasswordNeeded"] === true) {
|
if (loginDataOld["legacyPasswordNeeded"] === true) {
|
||||||
await addLegacyPassword(username, await hashpass(await hashpassold(password)))
|
await addLegacyPassword(username, await hashpass(await hashpassold(password)))
|
||||||
}
|
}
|
||||||
await migrateLegacyPassword(loginDataOld["key"], await hashpass(password))
|
await migrateLegacyPassword(loginDataOld["key"], hashedPass)
|
||||||
window.location.replace("/app/")
|
window.location.replace("/app/")
|
||||||
} else {
|
} else {
|
||||||
statusBox.innerText = loginDataOld["error"]
|
statusBox.innerText = loginDataOld["error"]
|
||||||
|
@ -211,4 +235,4 @@ backButton.addEventListener("click", () => {
|
||||||
|
|
||||||
showInput(0)
|
showInput(0)
|
||||||
|
|
||||||
// @license-endc
|
// @license-end
|
|
@ -22,7 +22,6 @@ function formatBytes(a, b = 2) { if (!+a) return "0 Bytes"; const c = 0 > b ? 0
|
||||||
let secretkey = localStorage.getItem("DONOTSHARE-secretkey")
|
let secretkey = localStorage.getItem("DONOTSHARE-secretkey")
|
||||||
let password = localStorage.getItem("DONOTSHARE-password")
|
let password = localStorage.getItem("DONOTSHARE-password")
|
||||||
let currentFontSize = 16
|
let currentFontSize = 16
|
||||||
let markdowntoggle = false
|
|
||||||
|
|
||||||
let backButton = document.getElementById("backButton")
|
let backButton = document.getElementById("backButton")
|
||||||
let usernameBox = document.getElementById("usernameBox")
|
let usernameBox = document.getElementById("usernameBox")
|
||||||
|
@ -51,11 +50,10 @@ let notesBar = document.getElementById("notesBar")
|
||||||
let topBar = document.getElementById("topBar")
|
let topBar = document.getElementById("topBar")
|
||||||
let notesDiv = document.getElementById("notesDiv")
|
let notesDiv = document.getElementById("notesDiv")
|
||||||
let newNote = document.getElementById("newNote")
|
let newNote = document.getElementById("newNote")
|
||||||
let noteBox = document.getElementById("noteBox")
|
|
||||||
let noteBoxDiv = document.getElementById("noteBoxDiv")
|
let noteBoxDiv = document.getElementById("noteBoxDiv")
|
||||||
|
let pellAttacher = document.getElementById("noteBox")
|
||||||
let loadingStuff = document.getElementById("loadingStuff")
|
let loadingStuff = document.getElementById("loadingStuff")
|
||||||
let exportNotesButton = document.getElementById("exportNotesButton")
|
let exportNotesButton = document.getElementById("exportNotesButton")
|
||||||
let markdown = document.getElementById('markdown');
|
|
||||||
let textSizeBox = document.getElementById('textSizeBox');
|
let textSizeBox = document.getElementById('textSizeBox');
|
||||||
let textPlusBox = document.getElementById('textPlusBox');
|
let textPlusBox = document.getElementById('textPlusBox');
|
||||||
let textMinusBox = document.getElementById('textMinusBox');
|
let textMinusBox = document.getElementById('textMinusBox');
|
||||||
|
@ -70,9 +68,25 @@ let indiv = false
|
||||||
let mobile = false
|
let mobile = false
|
||||||
let selectLatestNote = false
|
let selectLatestNote = false
|
||||||
|
|
||||||
|
// Init the note box
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
pell.init({
|
||||||
|
element: pellAttacher,
|
||||||
|
onChange: html => console.log(html),
|
||||||
|
defaultParagraphSeparator: 'br',
|
||||||
|
styleWithCSS: false,
|
||||||
|
classes: {
|
||||||
|
actionbar: 'pell-actionbar',
|
||||||
|
button: 'pell-button',
|
||||||
|
content: 'pell-content',
|
||||||
|
selected: 'pell-button-selected'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let noteBox = document.getElementsByClassName("pell-content")[0]
|
||||||
|
|
||||||
if (/Android|iPhone|iPod/i.test(navigator.userAgent)) {
|
if (/Android|iPhone|iPod/i.test(navigator.userAgent)) {
|
||||||
mobile = true
|
mobile = true
|
||||||
noteBoxDiv.classList.add("mobile");
|
noteBoxDiv.classList.add("mobile")
|
||||||
noteBoxDiv.style.width = "0px";
|
noteBoxDiv.style.width = "0px";
|
||||||
notesBar.style.width = "100%"
|
notesBar.style.width = "100%"
|
||||||
topBar.style.width = "100%"
|
topBar.style.width = "100%"
|
||||||
|
@ -104,28 +118,6 @@ if (/Android|iPhone|iPod/i.test(navigator.userAgent)) {
|
||||||
touchendY = event.changedTouches[0].screenY;
|
touchendY = event.changedTouches[0].screenY;
|
||||||
if (touchendX > touchstartX + 75) {
|
if (touchendX > touchstartX + 75) {
|
||||||
handleGesture();
|
handleGesture();
|
||||||
} else if (touchendX < touchstartX - 75) {
|
|
||||||
enableMarkdown();
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
markdown.addEventListener("touchstart", function (event) {
|
|
||||||
touchstartX = event.changedTouches[0].screenX;
|
|
||||||
touchstartY = event.changedTouches[0].screenY;
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
markdown.addEventListener("touchstart", function (event) {
|
|
||||||
touchstartX = event.changedTouches[0].screenX;
|
|
||||||
touchstartY = event.changedTouches[0].screenY;
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
markdown.addEventListener("touchend", function (event) {
|
|
||||||
touchendX = event.changedTouches[0].screenX;
|
|
||||||
touchendY = event.changedTouches[0].screenY;
|
|
||||||
if (touchendX > touchstartX + 75) {
|
|
||||||
disableMarkdown();
|
|
||||||
} else if (touchendX < touchstartX - 75) {
|
|
||||||
disableMarkdown();
|
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
@ -156,7 +148,7 @@ function handleGesture() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noteBox.value = ""
|
noteBox.innerText = ""
|
||||||
noteBox.readOnly = true
|
noteBox.readOnly = true
|
||||||
|
|
||||||
let noteCount = 0
|
let noteCount = 0
|
||||||
|
@ -183,13 +175,16 @@ function updateFont() {
|
||||||
currentFontSize = localStorage.getItem("SETTING-fontsize")
|
currentFontSize = localStorage.getItem("SETTING-fontsize")
|
||||||
noteBox.style.fontSize = currentFontSize + "px"
|
noteBox.style.fontSize = currentFontSize + "px"
|
||||||
textSizeBox.innerText = currentFontSize + "px"
|
textSizeBox.innerText = currentFontSize + "px"
|
||||||
if (markdowntoggle) {
|
|
||||||
markdown.srcdoc = "<!DOCTYPE html><html lang='en'><style>html { height: 100% } pre { white-space: pre-wrap; overflow-wrap: break-word; } body { white-space: pre-wrap; overflow-wrap: break-word; font-family: 'Inter', sans-serif; height: 100%; color: " + getComputedStyle(document.documentElement).getPropertyValue('--text-color') + "; font-size: " + currentFontSize + "px; }</style>" + marked.parse(DOMPurify.sanitize(noteBox.value)) + "</html>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checknetwork() {
|
async function checknetwork() {
|
||||||
fetch(remote + "/api/loggedin", {
|
let loggedInEndpoint
|
||||||
|
if (localStorage.getItem("legacy") === "true") {
|
||||||
|
loggedInEndpoint = "userinfo"
|
||||||
|
} else {
|
||||||
|
loggedInEndpoint = "loggedin"
|
||||||
|
}
|
||||||
|
fetch(remote + "/api/" + loggedInEndpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
secretKey: localStorage.getItem("DONOTSHARE-secretkey"),
|
secretKey: localStorage.getItem("DONOTSHARE-secretkey"),
|
||||||
|
@ -199,9 +194,8 @@ async function checknetwork() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
noteBox.readOnly = true
|
noteBox.contentEditable = false
|
||||||
noteBox.value = ""
|
noteBox.innerHTML = "<h1>You are currently offline.</h1>"
|
||||||
noteBox.placeholder = "You are currently offline."
|
|
||||||
displayError("Failed to connect to the server.\nPlease check your internet connection.")
|
displayError("Failed to connect to the server.\nPlease check your internet connection.")
|
||||||
})
|
})
|
||||||
.then((response) => response)
|
.then((response) => response)
|
||||||
|
@ -217,8 +211,7 @@ async function checknetwork() {
|
||||||
updateUserInfo()
|
updateUserInfo()
|
||||||
} else {
|
} else {
|
||||||
noteBox.readOnly = true
|
noteBox.readOnly = true
|
||||||
noteBox.value = ""
|
noteBox.innerHTML = "<h1>You are currently offline.</h1>"
|
||||||
noteBox.placeholder = "You are currently offline."
|
|
||||||
displayError("Failed to connect to the server.\nPlease check your internet connection.")
|
displayError("Failed to connect to the server.\nPlease check your internet connection.")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -277,9 +270,11 @@ function updateUserInfo() {
|
||||||
noteCount = responseData["notecount"]
|
noteCount = responseData["notecount"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doStuff()
|
doStuff()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameBox.addEventListener("click", () => {
|
usernameBox.addEventListener("click", () => {
|
||||||
optionsCoverDiv.classList.remove("hidden")
|
optionsCoverDiv.classList.remove("hidden")
|
||||||
optionsDiv.classList.remove("hidden")
|
optionsDiv.classList.remove("hidden")
|
||||||
|
@ -312,6 +307,7 @@ deleteMyAccountButton.addEventListener("click", () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function waitForConfirm() {
|
async function waitForConfirm() {
|
||||||
let resolvePromise;
|
let resolvePromise;
|
||||||
const promise = new Promise(resolve => resolvePromise = resolve);
|
const promise = new Promise(resolve => resolvePromise = resolve);
|
||||||
|
@ -331,6 +327,7 @@ async function hashpass(pass) {
|
||||||
|
|
||||||
changePasswordButton.addEventListener("click", () => {
|
changePasswordButton.addEventListener("click", () => {
|
||||||
optionsDiv.classList.add("hidden")
|
optionsDiv.classList.add("hidden")
|
||||||
|
|
||||||
async function doStuff() {
|
async function doStuff() {
|
||||||
async function fatalError(notes, passwordBackup) {
|
async function fatalError(notes, passwordBackup) {
|
||||||
displayError("Something went wrong! Your password change has failed. Attempting to revert changes...")
|
displayError("Something went wrong! Your password change has failed. Attempting to revert changes...")
|
||||||
|
@ -364,6 +361,7 @@ changePasswordButton.addEventListener("click", () => {
|
||||||
downloadObjectAsJson(notes, "data")
|
downloadObjectAsJson(notes, "data")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayError("Confirm your current password to change it")
|
displayError("Confirm your current password to change it")
|
||||||
errorInput.type = "password"
|
errorInput.type = "password"
|
||||||
errorInput.classList.remove("hidden")
|
errorInput.classList.remove("hidden")
|
||||||
|
@ -433,6 +431,7 @@ changePasswordButton.addEventListener("click", () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doStuff()
|
doStuff()
|
||||||
})
|
})
|
||||||
importNotesButton.addEventListener("click", () => {
|
importNotesButton.addEventListener("click", () => {
|
||||||
|
@ -510,6 +509,7 @@ sessionManagerButton.addEventListener("click", () => {
|
||||||
sessionDiv.append(sessionElement)
|
sessionDiv.append(sessionElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doStuff()
|
doStuff()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -523,19 +523,13 @@ exitSessionsThing.addEventListener("click", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateWordCount() {
|
function updateWordCount() {
|
||||||
let wordCount = noteBox.value.split(" ").length
|
let wordCount = noteBox.innerText.split(" ").length
|
||||||
if (wordCount === 1) {
|
if (wordCount === 1) {
|
||||||
wordCount = 0
|
wordCount = 0
|
||||||
}
|
}
|
||||||
wordCountBox.innerText = wordCount + " words"
|
wordCountBox.innerText = wordCount + " words"
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMarkDown() {
|
|
||||||
if (markdowntoggle) {
|
|
||||||
markdown.srcdoc = "<!DOCTYPE html><html lang='en'><style>html { height: 100% } pre { white-space: pre-wrap; overflow-wrap: break-word; } body { white-space: pre-wrap; overflow-wrap: break-word; font-family: 'Inter', sans-serif; height: 100%; color: " + getComputedStyle(document.documentElement).getPropertyValue('--text-color') + "; font-size: " + currentFontSize + "px; }</style>" + marked.parse(DOMPurify.sanitize(noteBox.value)) + "</html>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectNote(nameithink) {
|
function selectNote(nameithink) {
|
||||||
document.querySelectorAll(".noteButton").forEach((el) => el.classList.remove("selected"));
|
document.querySelectorAll(".noteButton").forEach((el) => el.classList.remove("selected"));
|
||||||
let thingArray = Array.from(document.querySelectorAll(".noteButton")).find(el => String(nameithink) === String(el.id));
|
let thingArray = Array.from(document.querySelectorAll(".noteButton")).find(el => String(nameithink) === String(el.id));
|
||||||
|
@ -552,9 +546,8 @@ function selectNote(nameithink) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
noteBox.readOnly = true
|
noteBox.contentEditable = false
|
||||||
noteBox.value = ""
|
noteBox.innerHTML = ""
|
||||||
noteBox.placeholder = ""
|
|
||||||
displayError("Something went wrong... Please try again later!")
|
displayError("Something went wrong... Please try again later!")
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
@ -562,33 +555,32 @@ function selectNote(nameithink) {
|
||||||
if (mobile) {
|
if (mobile) {
|
||||||
handleGesture()
|
handleGesture()
|
||||||
}
|
}
|
||||||
noteBox.readOnly = false
|
noteBox.contentEditable = true
|
||||||
noteBox.placeholder = "Type something!"
|
noteBox.click()
|
||||||
|
|
||||||
async function doStuff() {
|
async function doStuff() {
|
||||||
let responseData = await response.json()
|
let responseData = await response.json()
|
||||||
|
|
||||||
let bytes = CryptoJS.AES.decrypt(responseData["content"], password);
|
let bytes = CryptoJS.AES.decrypt(responseData["content"], password);
|
||||||
noteBox.value = bytes.toString(CryptoJS.enc.Utf8)
|
let cleanedHTML = bytes.toString(CryptoJS.enc.Utf8).replace(/<(?!\/?(h1|h2|br)\b)[^>]*>/gi, '')
|
||||||
|
noteBox.innerHTML = cleanedHTML.replace("\n", "<br>")
|
||||||
|
|
||||||
updateWordCount()
|
updateWordCount()
|
||||||
renderMarkDown()
|
|
||||||
|
|
||||||
noteBox.addEventListener("input", () => {
|
noteBox.addEventListener("input", () => {
|
||||||
updateWordCount()
|
updateWordCount()
|
||||||
renderMarkDown()
|
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
let preEncryptedTitle = noteBox.value
|
let preEncryptedTitle = noteBox.innerText
|
||||||
|
|
||||||
if (noteBox.value.substring(0, noteBox.value.indexOf("\n")) !== "") {
|
if (noteBox.innerText.substring(0, noteBox.innerText.indexOf("\n")) !== "") {
|
||||||
preEncryptedTitle = noteBox.value.substring(0, noteBox.value.indexOf("\n"));
|
preEncryptedTitle = noteBox.innerText.substring(0, noteBox.innerText.indexOf("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
preEncryptedTitle = truncateString(preEncryptedTitle, 15)
|
preEncryptedTitle = truncateString(preEncryptedTitle, 15)
|
||||||
document.getElementById(nameithink).innerText = preEncryptedTitle
|
document.getElementById(nameithink).innerText = preEncryptedTitle
|
||||||
|
|
||||||
let encryptedText = CryptoJS.AES.encrypt(noteBox.value, password).toString();
|
let encryptedText = CryptoJS.AES.encrypt(noteBox.innerHTML, password).toString();
|
||||||
let encryptedTitle = CryptoJS.AES.encrypt(preEncryptedTitle, password).toString();
|
let encryptedTitle = CryptoJS.AES.encrypt(preEncryptedTitle, password).toString();
|
||||||
|
|
||||||
console.log(encryptedTitle)
|
console.log(encryptedTitle)
|
||||||
|
@ -619,12 +611,13 @@ function selectNote(nameithink) {
|
||||||
}, waitTime);
|
}, waitTime);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doStuff()
|
doStuff()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNotes() {
|
function updateNotes() {
|
||||||
console.log("notes updated")
|
console.log("Notes updated")
|
||||||
fetch(remote + "/api/listnotes", {
|
fetch(remote + "/api/listnotes", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -636,23 +629,16 @@ function updateNotes() {
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
async function doStuff() {
|
async function doStuff() {
|
||||||
noteBox.readOnly = true
|
noteBox.contentEditable = false
|
||||||
selectedNote = 0
|
selectedNote = 0
|
||||||
if (selectLatestNote === false) {
|
noteBox.innerHTML = ""
|
||||||
noteBox.placeholder = ""
|
|
||||||
}
|
|
||||||
noteBox.value = ""
|
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
updateWordCount()
|
updateWordCount()
|
||||||
renderMarkDown()
|
|
||||||
|
|
||||||
let responseData = await response.json()
|
let responseData = await response.json()
|
||||||
|
|
||||||
let decryptedResponseData = []
|
let decryptedResponseData = []
|
||||||
|
|
||||||
let highestID = 0
|
let highestID = 0
|
||||||
|
|
||||||
// First decrypt note data, then render
|
|
||||||
let noteData;
|
let noteData;
|
||||||
for (let i in responseData) {
|
for (let i in responseData) {
|
||||||
noteData = responseData[i]
|
noteData = responseData[i]
|
||||||
|
@ -717,6 +703,7 @@ function updateNotes() {
|
||||||
selectLatestNote = false
|
selectLatestNote = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doStuff()
|
doStuff()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -729,7 +716,6 @@ newNote.addEventListener("click", () => {
|
||||||
console.log(selectLatestNote)
|
console.log(selectLatestNote)
|
||||||
|
|
||||||
// create fake item
|
// create fake item
|
||||||
|
|
||||||
document.querySelectorAll(".noteButton").forEach((el) => el.classList.remove("selected"));
|
document.querySelectorAll(".noteButton").forEach((el) => el.classList.remove("selected"));
|
||||||
let noteButton = document.createElement("button");
|
let noteButton = document.createElement("button");
|
||||||
noteButton.classList.add("noteButton")
|
noteButton.classList.add("noteButton")
|
||||||
|
@ -737,7 +723,7 @@ newNote.addEventListener("click", () => {
|
||||||
noteButton.innerText = "New note"
|
noteButton.innerText = "New note"
|
||||||
noteButton.style.order = "-1"
|
noteButton.style.order = "-1"
|
||||||
noteButton.classList.add("selected")
|
noteButton.classList.add("selected")
|
||||||
noteBox.placeholder = "Type something!"
|
noteBox.click()
|
||||||
|
|
||||||
let encryptedName = CryptoJS.AES.encrypt(noteName, password).toString(CryptoJS.enc.Utf8);
|
let encryptedName = CryptoJS.AES.encrypt(noteName, password).toString(CryptoJS.enc.Utf8);
|
||||||
|
|
||||||
|
@ -763,6 +749,7 @@ newNote.addEventListener("click", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function downloadObjectAsJson(exportObj, exportName) {
|
function downloadObjectAsJson(exportObj, exportName) {
|
||||||
let dataStr = "data:text/json;charset=utf-8," + JSON.stringify(exportObj);
|
let dataStr = "data:text/json;charset=utf-8," + JSON.stringify(exportObj);
|
||||||
let downloadAnchorNode = document.createElement("a");
|
let downloadAnchorNode = document.createElement("a");
|
||||||
|
@ -826,26 +813,6 @@ function firstNewVersion() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMarkdown() {
|
|
||||||
if (markdown.style.display === 'none') {
|
|
||||||
enableMarkdown()
|
|
||||||
} else {
|
|
||||||
disableMarkdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableMarkdown() {
|
|
||||||
markdown.style.display = 'inherit';
|
|
||||||
markdowntoggle = true
|
|
||||||
renderMarkDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
function disableMarkdown() {
|
|
||||||
markdown.style.display = 'none';
|
|
||||||
markdowntoggle = false
|
|
||||||
markdown.srcdoc = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
exportNotesButton.addEventListener("click", () => {
|
exportNotesButton.addEventListener("click", () => {
|
||||||
let responseData = exportNotes()
|
let responseData = exportNotes()
|
||||||
downloadObjectAsJson(responseData, "data")
|
downloadObjectAsJson(responseData, "data")
|
||||||
|
@ -903,14 +870,10 @@ removeBox.addEventListener("click", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
markdown.srcdoc = "<!DOCTYPE html><html lang='en'><style>html { height: 100% } pre { white-space: pre-wrap; overflow-wrap: break-word; } body { white-space: pre-wrap; overflow-wrap: break-word; font-family: 'Inter', sans-serif; height: 100%; color: " + getComputedStyle(document.documentElement).getPropertyValue('--text-color') + "; font-size: " + currentFontSize + "px; }</style>" + marked.parse(DOMPurify.sanitize(noteBox.value)) + "</html>"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (firstNewVersion()) {
|
if (firstNewVersion()) {
|
||||||
displayError("What's new in Burgernotes 2.0?\nRestyled client\nAdded changing passwords\nMigrated to OAuth2\nAdded importing notes")
|
displayError("What's new in Burgernotes 2.0?\nRestyled client\nAdded changing passwords\nAdded importing notes")
|
||||||
}
|
}
|
||||||
|
|
||||||
checknetwork()
|
checknetwork()
|
||||||
|
})
|
||||||
// @license-end
|
// @license-end
|
||||||
|
|
1301
static/js/marked.js
1301
static/js/marked.js
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,200 @@
|
||||||
|
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Beautified version of:
|
||||||
|
* pell (https://github.com/jaredreich/pell)
|
||||||
|
* (c) Jared Reich
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
! function(t, e) {
|
||||||
|
"object" == typeof exports && "undefined" != typeof module ? e(exports) : "function" == typeof define && define.amd ? define(["exports"], e) : e(t.pell = {})
|
||||||
|
}(this, function(t) {
|
||||||
|
"use strict";
|
||||||
|
var e = Object.assign || function(t) {
|
||||||
|
for (var e = 1; e < arguments.length; e++) {
|
||||||
|
var n = arguments[e];
|
||||||
|
for (var r in n) Object.prototype.hasOwnProperty.call(n, r) && (t[r] = n[r])
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
},
|
||||||
|
c = "defaultParagraphSeparator",
|
||||||
|
l = "formatBlock",
|
||||||
|
a = function(t, e, n) {
|
||||||
|
return t.addEventListener(e, n)
|
||||||
|
},
|
||||||
|
s = function(t, e) {
|
||||||
|
return t.appendChild(e)
|
||||||
|
},
|
||||||
|
d = function(t) {
|
||||||
|
return document.createElement(t)
|
||||||
|
},
|
||||||
|
n = function(t) {
|
||||||
|
return document.queryCommandState(t)
|
||||||
|
},
|
||||||
|
f = function(t) {
|
||||||
|
var e = 1 < arguments.length && void 0 !== arguments[1] ? arguments[1] : null;
|
||||||
|
return document.execCommand(t, !1, e)
|
||||||
|
},
|
||||||
|
p = {
|
||||||
|
bold: {
|
||||||
|
icon: "<b>B</b>",
|
||||||
|
title: "Bold",
|
||||||
|
state: function() {
|
||||||
|
return n("bold")
|
||||||
|
},
|
||||||
|
result: function() {
|
||||||
|
return f("bold")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
italic: {
|
||||||
|
icon: "<i>I</i>",
|
||||||
|
title: "Italic",
|
||||||
|
state: function() {
|
||||||
|
return n("italic")
|
||||||
|
},
|
||||||
|
result: function() {
|
||||||
|
return f("italic")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
underline: {
|
||||||
|
icon: "<u>U</u>",
|
||||||
|
title: "Underline",
|
||||||
|
state: function() {
|
||||||
|
return n("underline")
|
||||||
|
},
|
||||||
|
result: function() {
|
||||||
|
return f("underline")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
strikethrough: {
|
||||||
|
icon: "<strike>S</strike>",
|
||||||
|
title: "Strike-through",
|
||||||
|
state: function() {
|
||||||
|
return n("strikeThrough")
|
||||||
|
},
|
||||||
|
result: function() {
|
||||||
|
return f("strikeThrough")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
heading1: {
|
||||||
|
icon: "<b>H<sub>1</sub></b>",
|
||||||
|
title: "Heading 1",
|
||||||
|
result: function() {
|
||||||
|
return f(l, "<h1>")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
heading2: {
|
||||||
|
icon: "<b>H<sub>2</sub></b>",
|
||||||
|
title: "Heading 2",
|
||||||
|
result: function() {
|
||||||
|
return f(l, "<h2>")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
paragraph: {
|
||||||
|
icon: "¶",
|
||||||
|
title: "Paragraph",
|
||||||
|
result: function() {
|
||||||
|
return f(l, "<p>")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote: {
|
||||||
|
icon: "“ ”",
|
||||||
|
title: "Quote",
|
||||||
|
result: function() {
|
||||||
|
return f(l, "<blockquote>")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
olist: {
|
||||||
|
icon: "#",
|
||||||
|
title: "Ordered List",
|
||||||
|
result: function() {
|
||||||
|
return f("insertOrderedList")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ulist: {
|
||||||
|
icon: "•",
|
||||||
|
title: "Unordered List",
|
||||||
|
result: function() {
|
||||||
|
return f("insertUnorderedList")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
icon: "</>",
|
||||||
|
title: "Code",
|
||||||
|
result: function() {
|
||||||
|
return f(l, "<pre>")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
icon: "―",
|
||||||
|
title: "Horizontal Line",
|
||||||
|
result: function() {
|
||||||
|
return f("insertHorizontalRule")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
icon: "🔗",
|
||||||
|
title: "Link",
|
||||||
|
result: function() {
|
||||||
|
var t = window.prompt("Enter the link URL");
|
||||||
|
t && f("createLink", t)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
icon: "📷",
|
||||||
|
title: "Image",
|
||||||
|
result: function() {
|
||||||
|
var t = window.prompt("Enter the image URL");
|
||||||
|
t && f("insertImage", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
m = {
|
||||||
|
actionbar: "pell-actionbar",
|
||||||
|
button: "pell-button",
|
||||||
|
content: "pell-content",
|
||||||
|
selected: "pell-button-selected"
|
||||||
|
},
|
||||||
|
r = function(n) {
|
||||||
|
var t = n.actions ? n.actions.map(function(t) {
|
||||||
|
return "string" == typeof t ? p[t] : p[t.name] ? e({}, p[t.name], t) : t
|
||||||
|
}) : Object.keys(p).map(function(t) {
|
||||||
|
return p[t]
|
||||||
|
}),
|
||||||
|
r = e({}, m, n.classes),
|
||||||
|
i = n[c] || "div",
|
||||||
|
o = d("div");
|
||||||
|
o.className = r.actionbar, s(n.element, o);
|
||||||
|
var u = n.element.content = d("div");
|
||||||
|
return u.contentEditable = !0, u.className = r.content, u.oninput = function(t) {
|
||||||
|
var e = t.target.firstChild;
|
||||||
|
e && 3 === e.nodeType ? f(l, "<" + i + ">") : "<br>" === u.innerHTML && (u.innerHTML = ""), n.onChange(u.innerHTML)
|
||||||
|
}, u.onkeydown = function(t) {
|
||||||
|
var e;
|
||||||
|
"Enter" === t.key && "blockquote" === (e = l, document.queryCommandValue(e)) && setTimeout(function() {
|
||||||
|
return f(l, "<" + i + ">")
|
||||||
|
}, 0)
|
||||||
|
}, s(n.element, u), t.forEach(function(t) {
|
||||||
|
var e = d("button");
|
||||||
|
if (e.className = r.button, e.innerHTML = t.icon, e.title = t.title, e.setAttribute("type", "button"), e.onclick = function() {
|
||||||
|
return t.result() && u.focus()
|
||||||
|
}, t.state) {
|
||||||
|
var n = function() {
|
||||||
|
return e.classList[t.state() ? "add" : "remove"](r.selected)
|
||||||
|
};
|
||||||
|
a(u, "keyup", n), a(u, "mouseup", n), a(e, "click", n)
|
||||||
|
}
|
||||||
|
s(o, e)
|
||||||
|
}), n.styleWithCSS && f("styleWithCSS"), f(c, i), n.element
|
||||||
|
},
|
||||||
|
i = {
|
||||||
|
exec: f,
|
||||||
|
init: r
|
||||||
|
};
|
||||||
|
t.exec = f, t.init = r, t.default = i, Object.defineProperty(t, "__esModule", {
|
||||||
|
value: !0
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// @license-end
|
1565
static/js/purify.js
1565
static/js/purify.js
File diff suppressed because it is too large
Load Diff
10043
static/js/sodium-sumo.js
10043
static/js/sodium-sumo.js
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
||||||
121
|
200
|
Reference in New Issue