Switch Repo to notatio/notatio
This commit is contained in:
commit
9da51a1e6b
26 changed files with 4117 additions and 0 deletions
181
templates/edit.html
Normal file
181
templates/edit.html
Normal file
|
@ -0,0 +1,181 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en_US">
|
||||
|
||||
<head>
|
||||
<title>Edit File</title>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
</script>
|
||||
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
||||
<script src="/static/build/editor.js" type="text/javascript"></script>
|
||||
<style>
|
||||
.editor:empty:not(:focus):before {
|
||||
content: attr(data-placeholder);
|
||||
opacity: 45%;
|
||||
}
|
||||
|
||||
|
||||
[contenteditable]:focus {
|
||||
outline: 0px solid transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="vh-100">
|
||||
|
||||
<div class="sticky-top bg-light-grey">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="mx-auto d-flex justify-content-center">
|
||||
<h1>Editing File: </h1>
|
||||
<h1 id="filename" onclick="editFileName()" contenteditable="false">{{.Filename}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row sticky-top bg-light-grey">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between bg-light-grey border-bottom p-2">
|
||||
<div>
|
||||
<div class="dropdown d-inline-block">
|
||||
<button class="btn dropdown-toggle" type="button" id="headingDropdown"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-heading"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="headingDropdown">
|
||||
<li><button class="dropdown-item" type="button"
|
||||
onclick="formatText('heading', 'h1')">Heading 1</button></li>
|
||||
<li><button class="dropdown-item" type="button"
|
||||
onclick="formatText('heading', 'h2')">Heading 2</button></li>
|
||||
<li><button class="dropdown-item" type="button"
|
||||
onclick="formatText('heading', 'h3')">Heading 3</button></li>
|
||||
<li><button class="dropdown-item" type="button"
|
||||
onclick="formatText('heading', 'h4')">Heading 4</button></li>
|
||||
<li><button class="dropdown-item" type="button"
|
||||
onclick="formatText('heading', 'h5')">Heading 5</button></li>
|
||||
<li><button class="dropdown-item" type="button"
|
||||
onclick="formatText('heading', 'h6')">Heading 6</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('bold')" aria-label="Bold"><i
|
||||
class="fas fa-bold"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('italic')"><i
|
||||
class="fas fa-italic"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('underline')"><i
|
||||
class="fas fa-underline"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('strikeThrough')"><i
|
||||
class="fas fa-strikethrough"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('removeFormat')"><i
|
||||
class="fas fa-eraser"></i></button>
|
||||
<button class="btn btn-light" onclick="addTable()"><i class="fas fa-table"
|
||||
disabled></i></button>
|
||||
<button class="btn btn-light" onclick="addVideo()"><i class="fas fa-video"></i></button>
|
||||
|
||||
<button type="button" class="btn btn-light" onclick="undo()"><i
|
||||
class="fas fa-undo"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="redo()"><i
|
||||
class="fas fa-redo"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('insertUnorderedList')"><i
|
||||
class="fas fa-list-ul"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('insertOrderedList')"><i
|
||||
class="fas fa-list-ol"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="insertImage()"><i
|
||||
class="fas fa-image"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('insertHorizontalRule')"><i
|
||||
class="fas fa-grip-lines"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('formatBlock', '<blockquote>')"
|
||||
disabled><i class="fas fa-quote-right"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('formatBlock', '<code>')"
|
||||
disabled><i class="fas fa-code"></i></button>
|
||||
<button type="button" class="btn btn-light" onclick="formatText('insertHorizontalRule')"><i
|
||||
class="fas fa-grip-lines"></i></button>
|
||||
|
||||
|
||||
<button class="btn btn-light" onclick="insertLink()"><i class="fas fa-link"></i></button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<button class="btn btn-light" onclick="confirmOverwrite()"> <i class="fa fa-notes-medical"></i>
|
||||
Templates</i></button>
|
||||
<button class="btn btn-light" onclick="saveForm()"><i class="fas fa-save"></i></button>
|
||||
<button class="btn btn-light" onclick="confirmDeletion()"><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div id="template-modal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Templates</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Generate buttons for each template -->
|
||||
{{ range .Templates }}
|
||||
<button type="button" class="btn btn-primary"
|
||||
onclick="redirectToTemplate('/edit?filename={{.Filename}}&template={{.TemplateName}}')">
|
||||
{{.TemplateName}}
|
||||
</button><br>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<form id="save-form" method="post" action="/save?filename={{.Filename}}">
|
||||
<textarea hidden="true" id="editor-form-content" name="editor"></textarea>
|
||||
<input type="hidden" id="filename-form-content" name="filename" value="{{.Filename}}">
|
||||
<div class="editor shadow-none" id="editor" role="textbox" contenteditable="true"
|
||||
data-placeholder="The page lays unwritten....">
|
||||
{{.FileContent}}</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tableModal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add Table</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="tableRows">Rows</label>
|
||||
<input type="number" class="form-control" id="tableRows" value="3" min="2">
|
||||
</div>
|
||||
<div class="form-group col-sm-6">
|
||||
<label for="tableColumns">Columns</label>
|
||||
<input type="number" class="form-control" id="tableColumns" value="3" min="2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="document.getElementById('tableModal').style.display = 'none'">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="createTable()">Create Table</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
357
templates/index.html
Normal file
357
templates/index.html
Normal file
|
@ -0,0 +1,357 @@
|
|||
<!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">
|
||||
<div id="login-error" class="red"></div>
|
||||
<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>
|
146
templates/kanban.html
Normal file
146
templates/kanban.html
Normal file
|
@ -0,0 +1,146 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
<title>Kanban Board</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<h1>Kanban Board</h1>
|
||||
<form action="/update-task-status" method="POST" class="my-4">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Task Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Task Description</label>
|
||||
<textarea class="form-control" id="description" name="description" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">Status</label>
|
||||
<select class="form-select" id="status" name="status" required>
|
||||
<option value="todo">To Do</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
<option value="done">Done</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="task_id" id="task_id">
|
||||
<button type="submit" class="btn btn-primary" onclick="updateTaskStatus(event)">Add Task</button>
|
||||
</form>
|
||||
<h2>Tasks:</h2>
|
||||
<div class="row">
|
||||
<div class="col" id="todo">
|
||||
<h3>To Do</h3>
|
||||
<div class="sortable" data-status="todo">
|
||||
{{ range .Tasks }}
|
||||
{{ if eq .Status "todo" }}
|
||||
<div class="card my-2" data-id="{{ .ID }}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ .Title }}</h5>
|
||||
<p class="card-text">{{ .Description }}</p>
|
||||
<p class="card-text"><strong>Status:</strong> {{ .Status }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="in-progress">
|
||||
<h3>In Progress</h3>
|
||||
<div class="sortable" data-status="in_progress">
|
||||
{{ range .Tasks }}
|
||||
{{ if eq .Status "in_progress" }}
|
||||
<div class="card my-2" data-id="{{ .ID }}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ .Title }}</h5>
|
||||
<p class="card-text">{{ .Description }}</p>
|
||||
<p class="card-text"><strong>Status:</strong> {{ .Status }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="done">
|
||||
<h3>Done</h3>
|
||||
<div class="sortable" data-status="done">
|
||||
{{ range .Tasks }}
|
||||
{{ if eq .Status "done" }}
|
||||
<div class="card my-2" data-id="{{ .ID }}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ .Title }}</h5>
|
||||
<p class="card-text">{{ .Description }}</p>
|
||||
<p class="card-text"><strong>Status:</strong> {{ .Status }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.13.0/Sortable.min.js"></script>
|
||||
<script>
|
||||
function updateTaskStatus(event) {
|
||||
event.preventDefault();
|
||||
var form = event.target.closest('form');
|
||||
var taskId = form.querySelector('#task_id').value;
|
||||
var newStatus = form.querySelector('#status').value;
|
||||
fetch(form.action, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ id: taskId, status: newStatus })
|
||||
}).then(function () {
|
||||
// Update the task status in the card
|
||||
var card = form.closest('.card');
|
||||
card.querySelector('.card-text:last-child').innerHTML = '<strong>Status:</strong> ' + newStatus;
|
||||
}).catch(function (error) {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var sortableContainers = [].slice.call(document.querySelectorAll('.sortable'));
|
||||
sortableContainers.forEach(function (container) {
|
||||
new Sortable(container, {
|
||||
group: 'kanban',
|
||||
draggable: '.card',
|
||||
animation: 150,
|
||||
handle: '.card',
|
||||
onEnd: function (evt) {
|
||||
var taskId = evt.item.getAttribute('data-id');
|
||||
var newStatus = evt.to.getAttribute('data-status');
|
||||
|
||||
// Update the task status in the backend
|
||||
fetch('/update-task-status', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ id: taskId, status: newStatus })
|
||||
}).then(function (response) {
|
||||
if (response.ok) {
|
||||
// Update the task status in the card
|
||||
evt.item.querySelector('.card-text:last-child').innerHTML = '<strong>Status:</strong> ' + newStatus;
|
||||
} else {
|
||||
console.error('Failed to update task status:', response.status);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
212
templates/list.html
Normal file
212
templates/list.html
Normal file
|
@ -0,0 +1,212 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>List Files</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
||||
<script>
|
||||
// Function to convert Unix timestamps to local time
|
||||
function convertUnixTimeToLocalTime(unixTime, element) {
|
||||
const date = new Date(unixTime * 1000);
|
||||
element.textContent = date.toLocaleString();
|
||||
}
|
||||
|
||||
// Call the function for each timestamp on page load
|
||||
window.onload = function () {
|
||||
const creationTimeElements = document.querySelectorAll('.creation-time');
|
||||
const lastEditedTimeElements = document.querySelectorAll('.last-edited-time');
|
||||
|
||||
creationTimeElements.forEach(element => {
|
||||
const unixTime = parseInt(element.getAttribute('data-unix'));
|
||||
convertUnixTimeToLocalTime(unixTime, element);
|
||||
});
|
||||
|
||||
lastEditedTimeElements.forEach(element => {
|
||||
const unixTime = parseInt(element.getAttribute('data-unix'));
|
||||
convertUnixTimeToLocalTime(unixTime, element);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container" class="container">
|
||||
<h1>Welcome, {{.Username}}!</h1>
|
||||
|
||||
|
||||
|
||||
<!-- Upload Form Modal -->
|
||||
<div id="uploadModal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Upload File</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="/upload" enctype="multipart/form-data">
|
||||
<input type="file" name="file" id="file" accept=".md,.html" multiple required /><br /><br />
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New File Modal -->
|
||||
<div id="newFileModal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Create New File</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="newFileForm" method="post" action="/create">
|
||||
<label for="newFileName">File Name:</label>
|
||||
<input type="text" name="newFileName" id="newFileName" required><br /><br />
|
||||
<button type="submit" id="createNewFile" class="btn btn-primary">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="buttons-container" class="d-flex justify-content-between">
|
||||
<div>
|
||||
<button id="openUploadModal" class="btn btn-primary" data-bs-toggle="modal"
|
||||
data-bs-target="#uploadModal">Upload File</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="openNewFileModal" class="btn btn-primary" data-bs-toggle="modal"
|
||||
data-bs-target="#newFileModal">Create New File</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="openDeleteFileModal" class="btn btn-primary" data-bs-toggle="modal"
|
||||
data-bs-target="#deleteFileModal">Delete Files</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="exportFolder" class="btn btn-primary">Export Folder</button>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="location.href='/logout'" class="btn btn-primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete File Modal -->
|
||||
<div id="deleteFileModal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Delete Files</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="deleteForm" action="/delete" method="post">
|
||||
<div class="checklist">
|
||||
{{range .Files}}
|
||||
<label>
|
||||
<input type="checkbox" name="filesToDelete[]" value="{{.Filename}}">
|
||||
{{.Filename}}
|
||||
</label>
|
||||
<br>
|
||||
{{end}}
|
||||
</div>
|
||||
<button type="button" data-bs-dismiss="modal" class="btn btn-secondary">Cancel</button>
|
||||
<button type="button" id="checkAll" class="btn btn-primary">Check All</button>
|
||||
<button type="submit" id="deleteSelected" class="btn btn-danger">Delete Selected</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File Name</th>
|
||||
<th>Creation Time</th>
|
||||
<th>Last Edited</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Files}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/edit?filename={{.Filename}}">{{.Filename}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="creation-time" data-unix="{{.CreationTime}}"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="last-edited-time" data-unix="{{.LastEdited}}"></span>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
const deleteForm = document.getElementById("deleteForm");
|
||||
const checkAllButton = document.getElementById("checkAll");
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
||||
checkAllButton.addEventListener("click", function () {
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = true;
|
||||
});
|
||||
});
|
||||
|
||||
deleteForm.addEventListener("submit", function (event) {
|
||||
event.preventDefault(); // Stop form submission
|
||||
|
||||
// Get all checkboxes in the delete form
|
||||
const checkboxes = deleteForm.querySelectorAll('input[type="checkbox"]');
|
||||
const filesToDelete = [];
|
||||
|
||||
// Get the filenames of the selected files
|
||||
checkboxes.forEach(checkbox => {
|
||||
if (checkbox.checked) {
|
||||
filesToDelete.push(checkbox.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Call the deleteFiles function to send the request to the server
|
||||
deleteFiles(filesToDelete);
|
||||
});
|
||||
|
||||
|
||||
function deleteFiles(filesToDelete) {
|
||||
// Send a POST request to the server to delete the selected files
|
||||
fetch("/delete", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ files: filesToDelete }),
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
// Files deleted successfully
|
||||
location.reload(); // Refresh the page to reflect the changes
|
||||
} else {
|
||||
// Handle the error, e.g., display an error message
|
||||
console.error("Error deleting files");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error deleting files:", error);
|
||||
});
|
||||
}
|
||||
// Handle exportFolder button click
|
||||
const exportFolderButton = document.getElementById("exportFolder");
|
||||
exportFolderButton.addEventListener("click", function () {
|
||||
window.location.href = "/export";
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
24
templates/newuser.html
Normal file
24
templates/newuser.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Welcome to Notatio!</h2>
|
||||
<p>Before you begin creating your first file we want to give you a few tips!</p>
|
||||
<tip>Although E2EE is planned, It is not implemented yet so <strong>DO NOT</strong> store sensitive information.
|
||||
(The admins can read it)</tip>
|
||||
<tip>Use unique and meaningful file names, dates often dont convey what the file holds, nor does temp or document 1.
|
||||
</tip>
|
||||
<tip>Back up your files often if you are using a public instance! You never know If the admin will take the service
|
||||
down.</tip>
|
||||
<h4>Enter the name of your first file:</h4>
|
||||
<form id="newFileForm" method="post" action="/create">
|
||||
<input type="text" name="newFileName" id="newFileName" required><br><br>
|
||||
<button type="submit" id="createNewFile">Create</button>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue