forked from KaKi87/promptable-todo-list
227 lines
No EOL
9.2 KiB
HTML
227 lines
No EOL
9.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Promptable To-Do List</title>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
|
<style>
|
|
:root {
|
|
--pico-spacing: 0;
|
|
--pico-typography-spacing-vertical: 0;
|
|
--pico-form-element-spacing-vertical: 0;
|
|
--pico-form-element-spacing-horizontal: 0;
|
|
--pico-nav-element-spacing-vertical: 0;
|
|
--pico-nav-element-spacing-horizontal: 0;
|
|
--pico-nav-link-spacing-vertical: 0;
|
|
--pico-nav-link-spacing-horizontal: 0;
|
|
}
|
|
|
|
ul {
|
|
padding-left: 0;
|
|
}
|
|
input[type="checkbox"] {
|
|
margin: 0;
|
|
}
|
|
input[type="text"] {
|
|
padding: 0 0.25rem;
|
|
}
|
|
|
|
.app {
|
|
height: 100dvh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.app__header,
|
|
.app__main {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.app__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.app__main,
|
|
.main__tasks {
|
|
display: flex;
|
|
flex-direction: column;
|
|
row-gap: 1rem;
|
|
}
|
|
|
|
.app__main {
|
|
flex: 1;
|
|
}
|
|
|
|
.main__tasks__task,
|
|
.main__new-task {
|
|
display: flex;
|
|
align-items: center;
|
|
column-gap: 0.5rem;
|
|
}
|
|
|
|
.main__prompt {
|
|
margin-top: auto;
|
|
display: grid;
|
|
grid-template-columns: auto 1fr 100px;
|
|
column-gap: 0.5rem;
|
|
}
|
|
|
|
.main__prompt__submit {
|
|
padding: 0 0.5rem;
|
|
}
|
|
|
|
.main__prompt__submit[aria-busy] .main__prompt__submit__label {
|
|
display: none;
|
|
}
|
|
</style>
|
|
<script type="module">
|
|
import createSimpleElement from 'https://cdn.jsdelivr.net/npm/create-simple-element/createSimpleElement.js';
|
|
import axios from 'https://cdn.jsdelivr.net/npm/axios@1.9.0/dist/esm/axios.min.js';
|
|
|
|
let tasks = JSON.parse(window.localStorage.getItem('tasks') || '[]');
|
|
|
|
const
|
|
sortTasks = () => tasks.sort((a, b) => a.order - b.order),
|
|
saveTasks = () => window.localStorage.setItem('tasks', JSON.stringify(tasks)),
|
|
reloadTasks = () => {
|
|
sortTasks();
|
|
saveTasks();
|
|
|
|
const tasksElement = document.querySelector('.main__tasks');
|
|
|
|
tasksElement.innerHTML = '';
|
|
|
|
for(const task of tasks){
|
|
const taskElement = createSimpleElement('li.main__tasks__task');
|
|
|
|
const formElement = document.createElement('form');
|
|
formElement.setAttribute('style', 'display: contents');
|
|
taskElement.appendChild(formElement);
|
|
|
|
const isDoneElement = createSimpleElement('input[type="checkbox"]');
|
|
if(task.isDone)
|
|
isDoneElement.setAttribute('checked', '');
|
|
isDoneElement.addEventListener('input', () => {
|
|
task.isDone = isDoneElement.checked;
|
|
saveTasks();
|
|
});
|
|
formElement.appendChild(isDoneElement);
|
|
|
|
const titleElement = createSimpleElement('input[type="text"]');
|
|
titleElement.setAttribute('value', task.title);
|
|
formElement.appendChild(titleElement);
|
|
|
|
// TODO up/down
|
|
|
|
const removeElement = createSimpleElement('button.secondary');
|
|
removeElement.textContent = '🗑️';
|
|
removeElement.addEventListener('click', () => {
|
|
tasks.splice(tasks.indexOf(task), 1);
|
|
reloadTasks();
|
|
});
|
|
formElement.appendChild(removeElement);
|
|
|
|
// TODO replace form 'submit' event w/ field 'input' event
|
|
formElement.addEventListener('submit', event => {
|
|
event.preventDefault();
|
|
task.isDone = isDoneElement.checked;
|
|
task.title = titleElement.value;
|
|
saveTasks();
|
|
});
|
|
|
|
tasksElement.appendChild(taskElement);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
reloadTasks();
|
|
|
|
document.querySelector('.main__new-task').addEventListener('submit', event => {
|
|
event.preventDefault();
|
|
const
|
|
isDoneElement = document.querySelector('.main__new-task__is-done'),
|
|
titleElement = document.querySelector('.main__new-task__title');
|
|
tasks.push({
|
|
order: tasks.length ? Math.max(...tasks.map(task => task.order)) + 1 : 0,
|
|
isDone: isDoneElement.checked,
|
|
title: titleElement.value
|
|
});
|
|
isDoneElement.checked = false;
|
|
titleElement.value = '';
|
|
saveTasks();
|
|
reloadTasks();
|
|
});
|
|
|
|
const
|
|
promptFieldsetElement = document.querySelector('.main__prompt__fieldset'),
|
|
promptSubmitElement = document.querySelector('.main__prompt__submit'),
|
|
promptModelElement = document.querySelector('.main__prompt__model'),
|
|
promptTextElement = document.querySelector('.main__prompt__text');
|
|
|
|
document.querySelector('.main__prompt').addEventListener('submit', async event => {
|
|
event.preventDefault();
|
|
promptFieldsetElement.disabled = true;
|
|
promptSubmitElement.setAttribute('aria-busy', 'true');
|
|
let finalState;
|
|
try {
|
|
({
|
|
data: {
|
|
finalState
|
|
}
|
|
} = await axios.post('/', {
|
|
baseURL: 'http://localhost:11434/api',
|
|
model: promptModelElement.value,
|
|
initialState: tasks,
|
|
instructions: promptTextElement.value
|
|
}));
|
|
}
|
|
catch(error){
|
|
alert(
|
|
error.response?.data
|
|
? JSON.stringify(error.response.data, null, 4)
|
|
: error.stack
|
|
);
|
|
}
|
|
if(finalState){
|
|
tasks = finalState;
|
|
saveTasks();
|
|
reloadTasks();
|
|
promptTextElement.value = '';
|
|
}
|
|
promptSubmitElement.removeAttribute('aria-busy');
|
|
promptFieldsetElement.disabled = false;
|
|
});
|
|
|
|
promptModelElement.value = window.localStorage.getItem('model') || '';
|
|
promptModelElement.addEventListener('input', () => window.localStorage.setItem('model', promptModelElement.value));
|
|
});
|
|
</script>
|
|
</head>
|
|
<body class="app">
|
|
<header class="app__header">
|
|
<h1>Promptable To-Do List</h1>
|
|
<nav>
|
|
<ul>
|
|
<li><a href="https://git.kaki87.net/KaKi87/promptable-todo-list.git" target="_blank">Source code</a></li>
|
|
</ul>
|
|
</nav>
|
|
</header>
|
|
<main class="app__main">
|
|
<ul class="main__tasks"></ul>
|
|
<form class="main__new-task">
|
|
<label style="display: contents"><input class="main__new-task__is-done" type="checkbox"></label>
|
|
<label style="display: contents"><input class="main__new-task__title" type="text" placeholder="Manually add a new task..."></label>
|
|
</form>
|
|
<form class="main__prompt">
|
|
<fieldset class="main__prompt__fieldset" style="display: contents">
|
|
<label style="display: contents"><input class="main__prompt__model" type="text" placeholder="Model ID" required></label>
|
|
<label style="display: contents"><input class="main__prompt__text" type="text" placeholder="Prompt..." required></label>
|
|
<button class="main__prompt__submit" type="submit"><span class="main__prompt__submit__label">Submit</span></button>
|
|
</fieldset>
|
|
</form>
|
|
</main>
|
|
</body>
|
|
</html> |