2023-11-20 22:10:40 -06:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en_US">
|
|
|
|
|
|
|
|
<head>
|
|
|
|
<title>Notatio</title>
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
|
|
|
<!-- Font Awesome CSS for spinner -->
|
|
|
|
<link rel="stylesheet" type="text/css"
|
|
|
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
|
|
|
<link rel="stylesheet" type="text/css"
|
|
|
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
|
|
|
<script>
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
|
|
const passwordInput = document.getElementById("signup-password");
|
|
|
|
const passwordRequirements = document.querySelectorAll(".password-requirement");
|
|
|
|
|
|
|
|
passwordInput.addEventListener("input", function () {
|
|
|
|
const password = passwordInput.value;
|
|
|
|
|
|
|
|
passwordRequirements.forEach(function (requirement) {
|
|
|
|
const requirementRegex = new RegExp(requirement.dataset.regex);
|
|
|
|
if (requirementRegex.test(password)) {
|
|
|
|
requirement.classList.add("text-success");
|
|
|
|
} else {
|
|
|
|
requirement.classList.remove("text-success");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body class="d-flex flex-column min-vh-100 ">
|
|
|
|
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom p-2">
|
|
|
|
<a class="navbar-brand" href="/">
|
|
|
|
<img src="/static/favicon.ico" alt="" width="24" height="24" class="d-inline-block align-text-top">
|
|
|
|
Notatio
|
|
|
|
</a>
|
|
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
|
|
|
aria-expanded="false" aria-label="Toggle navigation">
|
|
|
|
<span class="navbar-toggler-icon"></span>
|
|
|
|
</button>
|
|
|
|
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
|
|
|
|
<ul class="navbar-nav">
|
|
|
|
<li class="nav-item">
|
|
|
|
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#loginModal">Login</a>
|
|
|
|
</li>
|
|
|
|
<li class="nav-item">
|
|
|
|
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#signupModal">Sign up</a>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
<main class="container">
|
|
|
|
<br>
|
|
|
|
<p>
|
|
|
|
Welcome to Notatio, an open source, web-based text editor written in the powerful Go programming language.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Notatio provides a user-friendly interface combined with robust features, making it the perfect choice for
|
|
|
|
developers, writers, and anyone who interacts with text on a daily basis.
|
|
|
|
</p>
|
|
|
|
<p><strong>Notatio is alpha software! Do no use it as your daily driver!</strong></p>
|
|
|
|
|
|
|
|
<h1 class="mt-4">Text Editor Comparison</h1>
|
|
|
|
<div class="table-responsive">
|
|
|
|
<table class="table table-striped">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th scope="col">Features</th>
|
|
|
|
<th scope="col">Notion.so</th>
|
|
|
|
<th scope="col">ObsidianMD</th>
|
|
|
|
<th scope="col">Notatio</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
<tr>
|
|
|
|
<td>Web-Based</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
<tr>
|
|
|
|
<td>Open Source</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td> <b>Does not</b> push paid plans</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>Uses Open File Formats</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>Sleek and Intuitive Interface</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>Collaborative Editing</td>
|
|
|
|
<td class="fs-4 text-success fw-bold">✓</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="planned">Planned</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>Syntax Highlighting</td>
|
|
|
|
<td class="fs-4 text-danger">✗</td>
|
|
|
|
<td class="fs-4 fw-bold text-success ">✓</td>
|
|
|
|
<td class=" planned">Planned</td>
|
|
|
|
</tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="modal fade" id="loginModal" tabindex="-1" aria-labelledby="loginModalLabel" aria-hidden="true">
|
|
|
|
<div class="modal-dialog">
|
|
|
|
<div class="modal-content">
|
|
|
|
<div class="modal-header">
|
|
|
|
<h3 class="modal-title" id="loginModalLabel">Log In</h3>
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
|
|
</div>
|
|
|
|
<form name="loginForm">
|
|
|
|
<div class="modal-body">
|
2023-11-21 15:24:25 -06:00
|
|
|
<div id="login-error" class="text-danger" role="alert"></div>
|
2023-11-20 22:10:40 -06:00
|
|
|
<label for="username">Username:</label>
|
|
|
|
<input type="text" id="username" name="username" required class="form-control"><br>
|
|
|
|
<label for="login_password">Password:</label>
|
|
|
|
<input type="password" id="login_password" name="login_password" required
|
|
|
|
class="form-control"><br>
|
|
|
|
</div>
|
|
|
|
<div class="modal-footer justify-content-between">
|
|
|
|
<p>
|
|
|
|
Don't have an account?
|
|
|
|
<a href="#" data-bs-toggle="modal" data-bs-target="#signupModal"
|
|
|
|
data-bs-dismiss="modal">Sign up</a>
|
|
|
|
</p>
|
|
|
|
<button type="submit" id="loginButton" class="btn btn-primary">Login</button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="modal fade" id="signupModal" tabindex="-1" aria-labelledby="signupModalLabel" aria-hidden="true">
|
|
|
|
<div class="modal-dialog">
|
|
|
|
<div class="modal-content">
|
|
|
|
<div class="modal-header">
|
|
|
|
<h3 class="modal-title" id="signupModalLabel">Sign Up</h3>
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
|
|
</div>
|
|
|
|
<form method="post" action="/signup" name="signupForm" class="was-validated">
|
|
|
|
<div class="modal-body">
|
|
|
|
<label for="name" class="form-label">Name:</label>
|
|
|
|
<input type="text" id="name" name="name" pattern="[a-zA-Z]{2,100}" required
|
|
|
|
class="form-control">
|
|
|
|
<div class="invalid-feedback">Please provide the name you would like to be addressed by.
|
|
|
|
</div><br>
|
|
|
|
<label for="email" class="form-label">Email:</label>
|
|
|
|
<input type="email" name="email" id="email" required class="form-control" >
|
|
|
|
<div class="invalid-feedback">Please provide a valid email for password recovery.</div>
|
|
|
|
<br>
|
|
|
|
<label for="username" class="form-label">Username:</label>
|
|
|
|
<div class="input-group has-validation">
|
|
|
|
<span class="input-group-text">@</span>
|
|
|
|
<input type="text" onblur="checkUsernameAvailability();" id="signup-username"
|
|
|
|
name="username" pattern="[a-zA-Z0-9_\-]{5,20}" required class="form-control"
|
|
|
|
data-bs-toggle="popover"
|
|
|
|
data-bs-content="Username must be 5-20 characters and can contain letters, numbers, underscores, or hyphens."
|
|
|
|
data-bs-trigger="focus">
|
|
|
|
<div class="invalid-feedback">Please select a username that consists of 5 to 20
|
|
|
|
alphanumeric characters (a-z and 0-9).</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<div id="usernameAvailability"></div>
|
|
|
|
<br>
|
|
|
|
<label for="password" class="form-label">Password:</label>
|
|
|
|
<input type="password" id="signup-password" name="password"
|
|
|
|
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!]).{10,}" required
|
|
|
|
class="form-control">
|
|
|
|
<div class="invalid-feedback">
|
|
|
|
Password must meet the following requirements:
|
|
|
|
<ul>
|
|
|
|
<li class="password-requirement" data-regex="\d">At least one digit (0-9)</li>
|
|
|
|
<li class="password-requirement" data-regex="[a-z]">At least one lowercase letter
|
|
|
|
(a-z)</li>
|
|
|
|
<li class="password-requirement" data-regex="[A-Z]">At least one uppercase letter
|
|
|
|
(A-Z)</li>
|
|
|
|
<li class="password-requirement" data-regex="[@#$%^&+=!?]">At least one special
|
|
|
|
symbol (@#$%^&+=!?)</li>
|
|
|
|
<li class="password-requirement" data-regex=".{10,}">At least 10 characters long
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="modal-footer justify-content-between">
|
|
|
|
<p>
|
|
|
|
Already have an account?
|
|
|
|
<a href="#" data-bs-toggle="modal" data-bs-target="#loginModal"
|
|
|
|
data-bs-dismiss="modal">Log
|
|
|
|
in</a>
|
|
|
|
</p>
|
|
|
|
<div class="mb-3">
|
|
|
|
<button class="btn btn-primary" type="submit" id="submitBtn" disabled>Submit
|
|
|
|
form</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</main>
|
|
|
|
<footer class="footer mt-auto">
|
|
|
|
<div class="container d-flex justify-content-center py-3 bg-light ">
|
|
|
|
<a href="https://codeberg.org/musselman/notatio#download" class="mx-2 text-muted">Download</a>
|
|
|
|
<a href="https://codeberg.org/Musselman/Notatio/wiki" class="mx-2 text-muted">Documentation</a>
|
|
|
|
<a href="https://codeberg.org/Musselman/Notatio/issues" class="mx-2 text-muted">Report Issues</a>
|
|
|
|
<a href="https://codeberg.org/Musselman/Notatio/sponsors" class="mx-2 text-muted">Sponsor</a>
|
|
|
|
</div>
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<script>
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
|
|
const signupForm = document.forms.signupForm;
|
|
|
|
const loginForm = document.forms.loginForm;
|
|
|
|
|
|
|
|
const submitBtn = document.getElementById("submitBtn");
|
|
|
|
|
|
|
|
signupForm.addEventListener("input", function () {
|
|
|
|
const formIsValid = signupForm.checkValidity();
|
|
|
|
submitBtn.disabled = !formIsValid;
|
|
|
|
});
|
|
|
|
|
|
|
|
loginForm.addEventListener("submit", function (event) {
|
|
|
|
event.preventDefault(); // Prevent the form from being submitted normally
|
|
|
|
submitLogin();
|
|
|
|
});
|
|
|
|
|
|
|
|
var urlParams = new URLSearchParams(window.location.search);
|
|
|
|
if (urlParams.has('login')) {
|
|
|
|
console.log("login parameter")
|
|
|
|
$('#loginModal').modal('show');
|
|
|
|
}
|
|
|
|
else if (urlParams.has('signup')) {
|
|
|
|
$('#signupModal').modal('show');
|
|
|
|
console.log("signup parameter")
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const usernameInput = document.getElementById("signup-username");
|
|
|
|
usernameInput.addEventListener("blur", function () {
|
|
|
|
checkUsernameAvailability();
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function checkUsernameAvailability() {
|
|
|
|
const usernameInput = document.getElementById("signup-username");
|
|
|
|
const availabilityDiv = document.getElementById("usernameAvailability");
|
|
|
|
|
|
|
|
// Get the value of the username input
|
|
|
|
const username = usernameInput.value;
|
|
|
|
|
|
|
|
// Check if the input value meets the required pattern
|
|
|
|
const usernameRegex = new RegExp(usernameInput.pattern);
|
|
|
|
if (!usernameRegex.test(username)) {
|
|
|
|
availabilityDiv.innerHTML = "";
|
|
|
|
return; // Exit the function if the username is not valid
|
|
|
|
}
|
|
|
|
// Send an AJAX request to the server
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.open("GET", "/checkusername?username=" + username, true);
|
|
|
|
xhr.onreadystatechange = function () {
|
|
|
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
|
|
const response = JSON.parse(xhr.responseText);
|
|
|
|
const isAvailable = response.available;
|
|
|
|
if (!isAvailable) {
|
|
|
|
availabilityDiv.innerHTML = "<span style='color:green;'>Username is available.</span>";
|
|
|
|
} else {
|
|
|
|
availabilityDiv.innerHTML = "<span style='color:red;'>Username is not available.</span>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
xhr.send();
|
|
|
|
}
|
|
|
|
|
|
|
|
function sleep(ms) {
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
}
|
|
|
|
function submitLogin() {
|
|
|
|
|
|
|
|
// Get the values of the username and password fields
|
|
|
|
const username = loginForm.elements.username.value;
|
|
|
|
const password = loginForm.elements.login_password.value;
|
|
|
|
|
|
|
|
// Create a new FormData object and append the values
|
|
|
|
const formData = new FormData();
|
|
|
|
formData.append("username", username);
|
|
|
|
formData.append("password", password);
|
|
|
|
// Send the login request as form data
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.open("POST", "/login", true);
|
|
|
|
|
|
|
|
xhr.onreadystatechange = function () {
|
|
|
|
if (xhr.readyState === 4) {
|
|
|
|
if (xhr.status === 200) {
|
|
|
|
// Login successful, redirect to home page
|
|
|
|
console.log("Waiting for redirect...")
|
|
|
|
window.location.href = "/home";
|
|
|
|
} else {
|
|
|
|
// Login failed, display the error message
|
|
|
|
const response = JSON.parse(xhr.responseText);
|
|
|
|
const loginErrorDiv = document.getElementById("login-error");
|
|
|
|
loginErrorDiv.textContent = response.error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
xhr.send(formData);
|
|
|
|
// Disable the button to prevent multiple clicks
|
|
|
|
loginButton.disabled = true;
|
|
|
|
|
|
|
|
// Show the spinner
|
|
|
|
const spinner = document.createElement('i');
|
|
|
|
spinner.classList.add('fas', 'fa-spinner', 'fa-spin');
|
|
|
|
loginButton.innerHTML = '';
|
|
|
|
loginButton.appendChild(spinner);
|
|
|
|
|
|
|
|
// Enable the button and remove the spinner after 5 seconds
|
|
|
|
setTimeout(function () {
|
|
|
|
loginButton.disabled = false;
|
|
|
|
loginButton.innerHTML = 'Login';
|
|
|
|
}, 5000);
|
|
|
|
sleep(5000).then(() => { loginButton.disabled = false; });
|
|
|
|
};
|
|
|
|
|
|
|
|
</script>
|
|
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
|
|
</body>
|
|
|
|
|
|
|
|
</html>
|