Tagghantering, fix #3

This commit is contained in:
2025-01-24 20:54:12 +01:00
parent eb5adae406
commit 6c72c02896
7 changed files with 177 additions and 21 deletions

View File

@@ -16,5 +16,8 @@ COPY . .
# Expose the port the app runs on
EXPOSE 3044
# Set the DEBUG environment variable
ENV DEBUG=app
# Command to run the application
CMD ["node", "server.js"]

80
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"dependencies": {
"basic-auth": "^2.0.1",
"body-parser": "^1.20.3",
"debug": "^4.4.0",
"express": "^4.21.2",
"fs": "^0.0.1-security"
}
@@ -71,6 +72,19 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/body-parser/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/body-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -139,11 +153,19 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dependencies": {
"ms": "2.0.0"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/depd": {
@@ -274,6 +296,19 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
@@ -291,6 +326,19 @@
"node": ">= 0.8"
}
},
"node_modules/finalhandler/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -490,9 +538,9 @@
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/negotiator": {
"version": "0.6.3",
@@ -632,6 +680,19 @@
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/send/node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -640,11 +701,6 @@
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",

View File

@@ -12,6 +12,7 @@
"dependencies": {
"basic-auth": "^2.0.1",
"body-parser": "^1.20.3",
"debug": "^4.4.0",
"express": "^4.21.2",
"fs": "^0.0.1-security"
}

View File

@@ -20,6 +20,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (sessionStorage.getItem('loggedIn') === 'true') {
loginContainer.style.display = 'none';
appContainer.style.display = 'block';
loadTags();
}
loginForm.addEventListener('submit', function(e) {
@@ -33,6 +34,7 @@ document.addEventListener('DOMContentLoaded', function() {
sessionStorage.setItem('loggedIn', 'true');
loginContainer.style.display = 'none';
appContainer.style.display = 'block';
loadTags();
} else {
loginMessage.textContent = 'Invalid username or password';
}
@@ -45,14 +47,31 @@ document.addEventListener('DOMContentLoaded', function() {
const subject = document.getElementById('subject').value;
const description = document.getElementById('description').value;
const scheduled = document.getElementById('scheduled').value;
const tagsInput = document.getElementById('tags').value;
const tags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag).join(':');
// Structure data for Org mode
const taskData = {
subject,
subject: `${subject} :${tags}:`,
description,
scheduled
};
// Save tags to server
const savedTags = JSON.parse(localStorage.getItem('tags')) || [];
const newTags = tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag && !savedTags.includes(tag));
const allTags = [...savedTags, ...newTags];
localStorage.setItem('tags', JSON.stringify(allTags));
fetch('/save-tags', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tags: allTags })
}).then(() => {
loadTags(); // Force refresh tags after saving
});
// Send data to backend using fetch
fetch('/add-task', {
method: 'POST',
@@ -82,4 +101,32 @@ document.addEventListener('DOMContentLoaded', function() {
weekNumbers: true, // Show week numbers
firstDayOfWeek: 1 // Start weeks on Monday
});
// Load tags from server and initialize autocomplete
function loadTags() {
fetch('/get-tags')
.then(response => response.json())
.then(tags => {
localStorage.setItem('tags', JSON.stringify(tags));
const autocompleteData = {};
tags.forEach(tag => {
autocompleteData[tag] = null; // Materialize autocomplete requires a key-value pair
});
const tagsInput = document.getElementById('tags');
M.Autocomplete.init(tagsInput, {
data: autocompleteData,
onAutocomplete: function(selectedTag) {
const currentTags = tagsInput.value.split(',').map(tag => tag.trim()).filter(tag => tag);
if (!currentTags.includes(selectedTag)) {
currentTags.push(selectedTag);
tagsInput.value = currentTags.join(', ');
}
}
});
})
.catch(error => {
console.error('Error loading tags:', error);
});
}
});

View File

@@ -47,6 +47,11 @@
<input type="date" id="scheduled" lang="sv-SE" required>
</div>
<div class="input-field">
<label for="tags">Tags (separated by commas):</label>
<input type="text" id="tags" class="autocomplete">
</div>
<button class="btn waves-effect waves-light" type="submit">Spara</button>
</form>
<p id="responseMessage" class="center-align"></p>

View File

@@ -15,9 +15,14 @@ self.addEventListener('install', (event) => {
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request);
})
);
if (event.request.url.includes('/get-tags') || event.request.url.includes('/save-tags')) {
// Bypass cache for tags endpoints
event.respondWith(fetch(event.request));
} else {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request);
})
);
}
});

View File

@@ -3,6 +3,7 @@ const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const basicAuth = require('basic-auth');
const debug = require('debug')('app');
const app = express();
const port = 3044;
@@ -11,11 +12,17 @@ app.use(bodyParser.json());
app.use(express.static('public'));
// Ensure the /data directory exists
const dataDir = path.join('/data');
const dataDir = '/data';
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
// Ensure the tags.json file exists
const tagsFilePath = path.join(dataDir, 'tags.json');
if (!fs.existsSync(tagsFilePath)) {
fs.writeFileSync(tagsFilePath, JSON.stringify([]));
}
// Authentication middleware
const auth = (req, res, next) => {
const user = basicAuth(req);
@@ -57,12 +64,44 @@ app.post('/add-task', auth, (req, res) => {
const filePath = path.join(dataDir, 'tasks.org');
fs.appendFile(filePath, orgFormattedData, (err) => {
if (err) {
debug('Error writing to file:', err);
return res.status(500).send('Error writing to file.');
}
res.send({ message: 'Task added successfully!' });
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
// Endpoint to save tags
app.post('/save-tags', auth, (req, res) => {
const { tags } = req.body;
const filePath = path.join(dataDir, 'tags.json');
fs.writeFile(filePath, JSON.stringify(tags), (err) => {
if (err) {
debug('Error saving tags:', err);
return res.status(500).send('Error saving tags.');
}
res.send({ message: 'Tags saved successfully!' });
});
});
// Endpoint to retrieve tags
app.get('/get-tags', auth, (req, res) => {
const filePath = path.join(dataDir, 'tags.json');
fs.readFile(filePath, (err, data) => {
if (err) {
debug('Error retrieving tags:', err);
return res.status(500).send('Error retrieving tags.');
}
try {
const tags = JSON.parse(data);
res.send(tags);
} catch (e) {
debug('Error parsing tags JSON:', e);
res.send([]);
}
});
});
app.listen(port, () => {
debug(`Server running at http://localhost:${port}`);
});