Commit b5496e99 authored by ransome1's avatar ransome1
Browse files

Added sugar.js to enhance due date definition, linting and clean up

parents cfd9a1bf c6a6bd2b
......@@ -114,3 +114,4 @@ A prioritized backlog of new features and known issues can be found <a href="htt
- i18next: https://github.com/i18next/i18next
- Matomo: https://github.com/matomo-org/matomo
- chokidar: https://github.com/paulmillr/chokidar
- Sugar: https://github.com/andrewplummer/Sugar
......@@ -118,6 +118,7 @@
"i18next-fs-backend": "^1.1.1",
"jstodotxt": "^0.10.0",
"marked": "^2.0.3",
"sugar": "^2.0.6",
"vanillajs-datepicker": "^1.1.4"
},
"devDependencies": {
......
export function createModalJail(modal) {
// add all the elements inside modal which you want to make focusable
const focusableElements = '[tabindex]:not([tabindex="-1"])';
const focusableElements = 'a.button, [tabindex]:not([tabindex="-1"])';
const firstFocusableElement = modal.querySelectorAll(focusableElements)[0]; // get first element to be focused inside modal
const focusableContent = modal.querySelectorAll(focusableElements);
const lastFocusableElement = focusableContent[focusableContent.length - 1]; // get last element to be focused inside modal
document.addEventListener("keydown", function(e) {
let isTabPressed = e.key === "Tab" || e.keyCode === 9;
document.addEventListener("keydown", function(event) {
let isTabPressed = event.key === "Tab" || event.keyCode === 9;
if (!isTabPressed) {
return;
}
if (e.shiftKey) { // if shift key pressed for shift + tab combination
if (event.shiftKey) { // if shift key pressed for shift + tab combination
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus(); // add focus for the last focusable element
e.preventDefault();
event.preventDefault();
}
} else { // if tab key is pressed
if (document.activeElement === lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
firstFocusableElement.focus(); // add focus for the first focusable element
e.preventDefault();
event.preventDefault();
}
}
});
......
......@@ -710,7 +710,7 @@ nav ul:nth-child(2) {
#autoCompleteContainer {
height: auto;
position: absolute;
position: fixed;
display: none;
z-index: 50;
opacity: 0.9;
......
......@@ -234,6 +234,7 @@
<div class="content">
<div class="control has-icons-right">
<input id="modalFormInput" class="input is-medium" type="text" tabindex="0">
<div id="autoCompleteContainer" class="drawer card"></div>
<a href="#" id="modalFormInputResize" class="icon is-right" tabindex="-1" data-input-type="input">
<i class="fas fa-expand-alt"></i>
</a>
......@@ -507,7 +508,7 @@
<td><a href="https://github.com/matomo-org/matomo" target="_blank">Matomo</a></td>
<td><a href="https://github.com/paulmillr/chokidar" target="_blank">chokidar</a></td>
<td><a href="https://github.com/viktor-shmigol/electron-windows-badge/" target="_blank">Electron Windows Badge</a></td>
<td></td>
<td><a href="https://github.com/andrewplummer/Sugar" target="_blank">Sugar</a></td>
</tr>
</table>
<p><code>v<span id="version"></span></code></p>
......@@ -662,7 +663,6 @@
<button class="modal-close close is-large" aria-label="close" tabindex="0"></button>
</div>
</div>
<div id="autoCompleteContainer" class="drawer card"></div>
<div id="todoContext" class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a id="todoContextUseAsTemplate" class="dropdown-item"></a>
......@@ -732,6 +732,7 @@
</p>
</article>
</section>
<script defer src="../node_modules/sugar/dist/sugar.min.js"></script>
<script defer type="module" src="render.js"></script>
</body>
</html>
......@@ -2,7 +2,7 @@
import { translations, userData } from "../render.js";
import { _paq } from "./matomo.mjs";
import { resizeInput } from "./form.mjs";
import { RecExtension } from "./todotxtExtensions.mjs";
import { RecExtension, SugarDueExtension } from "./todotxtExtensions.mjs";
import "../../node_modules/jstodotxt/jsTodoExtensions.js";
import "../../node_modules/jstodotxt/jsTodoTxt.js";
import Datepicker from "../../node_modules/vanillajs-datepicker/js/Datepicker.js";
......@@ -22,7 +22,7 @@ datePickerInput.addEventListener("changeDate", function (e) {
// we only update the object if there is a date selected. In case of a refresh it would throw an error otherwise
if(e.detail.date) {
// generate the object on what is written into input, so we don't overwrite previous inputs of user
let todo = new TodoTxtItem(modalFormInput.value, [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
let todo = new TodoTxtItem(document.getElementById("modalFormInput").value, [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
todo.due = new Date(e.detail.date);
todo.dueString = new Date(e.detail.date.getTime() - (e.detail.date.getTimezoneOffset() * 60000 )).toISOString().split("T")[0];
// if suggestion box was open, it needs to be closed
......@@ -54,7 +54,7 @@ const datePicker = new Datepicker(datePickerInput, {
}
});
document.querySelector(".datepicker .clear-btn").onclick = function() {
let todo = new TodoTxtItem(modalFormInput.value, [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
let todo = new TodoTxtItem(document.getElementById("modalFormInput").value, [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
todo.due = undefined;
todo.dueString = undefined;
modalFormInput.value = todo.toString();
......
......@@ -320,14 +320,17 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
// add the headline before category container
todoFiltersContainer.appendChild(todoFilterHeadline);
} else {
// show suggestion box
autoCompleteContainer.classList.add("is-active");
autoCompleteContainer.focus();
// create a sub headline element
let todoFilterHeadline = document.createElement("h4");
// show suggestion box when prefix is present
if(autoCompletePrefix!==undefined) {
autoCompleteContainer.classList.add("is-active");
autoCompleteContainer.focus();
}
todoFilterHeadline.setAttribute("tabindex", -1);
// create a sub headline element
todoFilterHeadline.setAttribute("class", "is-4 title");
// no need for tab index if the headline is in suggestion box
if(autoCompletePrefix==undefined) todoFilterHeadline.setAttribute("tabindex", -1);
//if(autoCompletePrefix==undefined)
todoFilterHeadline.innerHTML = headline;
// add the headline before category container
todoFiltersContainer.appendChild(todoFilterHeadline);
......@@ -342,8 +345,8 @@ function generateFilterButtons(category, autoCompleteValue, autoCompletePrefix,
if(category==="priority") todoFiltersItem.classList.add(filter);
todoFiltersItem.setAttribute("data-filter", filter);
todoFiltersItem.setAttribute("data-category", category);
if(autoCompletePrefix===undefined) { todoFiltersItem.setAttribute("tabindex", 0) } else { todoFiltersItem.setAttribute("tabindex", 301) }
todoFiltersItem.setAttribute("href", "#");
if(autoCompletePrefix===undefined) { todoFiltersItem.setAttribute("tabindex", 0) } else { todoFiltersItem.setAttribute("tabindex", 0) }
todoFiltersItem.innerHTML = filter;
if(autoCompletePrefix==undefined) {
// set highlighting if filter/category combination is on selected filters array
......
"use strict";
import { resetModal, handleError, userData, setUserData, translations } from "../render.js";
import { _paq } from "./matomo.mjs";
import { RecExtension } from "./todotxtExtensions.mjs";
import { RecExtension, SugarDueExtension } from "./todotxtExtensions.mjs";
import "../../node_modules/jstodotxt/jsTodoExtensions.js";
import "../../node_modules/jstodotxt/jsTodoTxt.js";
import { generateFilterData } from "./filters.mjs";
......@@ -45,6 +45,9 @@ modalFormInputResize.onclick = function() {
if(userData.matomoEvents) _paq.push(["trackEvent", "Form", "Click on Resize"]);
}
/*modalFormInput.addEventListener("keydown", event => {
if(event.key==="Tab" && document.getElementById("autoCompleteContainer").classList.contains("is-active")) document.getElementById("autoCompleteContainer").focus();
});*/
modalFormInput.addEventListener("keyup", event => {
// do not show suggestion container if Escape has been pressed
if(event.key==="Escape") return false;
......@@ -184,7 +187,7 @@ function setPriority(priority) {
});
}
}
let todo = new TodoTxtItem(modalFormInput.value, [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
let todo = new TodoTxtItem(document.getElementById("modalFormInput").value, [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
if((priority==="down" || priority==="up") && !todo.priority) {
todo.priority = "A";
} else if(priority==="up" && todo.priority!="a") {
......@@ -211,7 +214,7 @@ function setPriority(priority) {
}
function setDueDate(days) {
try {
const todo = new TodoTxtItem(modalFormInput.value, [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
const todo = new TodoTxtItem(document.getElementById("modalFormInput").value, [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
if(days===0) {
todo.due = undefined;
todo.dueString = undefined;
......@@ -247,7 +250,7 @@ function show(todo, templated) {
// replace invisible multiline ascii character with new line
todo = todo.replaceAll(String.fromCharCode(16),"\r\n");
// we need to check if there already is a due date in the object
todo = new TodoTxtItem(todo, [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
todo = new TodoTxtItem(todo, [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
// set the priority
setPriority(todo.priority);
//
......@@ -331,7 +334,7 @@ function submitForm() {
const index = items.objects.map(function(item) {return item.toString(); }).indexOf(modalForm.getAttribute("data-item"));
// create a todo.txt object
// replace new lines with spaces (https://stackoverflow.com/a/34936253)
let todo = new TodoTxtItem(modalForm.elements[0].value.replaceAll(/[\r\n]+/g, String.fromCharCode(16)), [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
let todo = new TodoTxtItem(modalForm.elements[0].value.replaceAll(/[\r\n]+/g, String.fromCharCode(16)), [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
// check and prevent duplicate todo
if(items.objects.map(function(item) {return item.toString(); }).indexOf(todo.toString())!=-1) {
modalFormAlert.innerHTML = translations.formInfoDuplicate;
......@@ -351,7 +354,7 @@ function submitForm() {
} else if(modalForm.getAttribute("data-item")==null && modalForm.elements[0].value!="") {
// in case there hasn't been a passed data item, we just push the input value as a new item into the array
// replace new lines with spaces (https://stackoverflow.com/a/34936253)
let todo = new TodoTxtItem(modalForm.elements[0].value.replaceAll(/[\r\n]+/g, String.fromCharCode(16)), [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
let todo = new TodoTxtItem(modalForm.elements[0].value.replaceAll(/[\r\n]+/g, String.fromCharCode(16)), [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
// we add the current date to the start date attribute of the todo.txt object
todo.date = new Date();
// check and prevent duplicate todo
......
......@@ -91,7 +91,7 @@ function showRecurrences() {
recurrencePickerContainer.focus();
recurrencePickerContainer.classList.toggle("is-active");
// get object from current input
let todo = new TodoTxtItem(modalFormInput.value, [ new DueExtension(), new HiddenExtension(), new RecExtension() ]);
let todo = new TodoTxtItem(document.getElementById("modalFormInput").value, [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]);
let recSplit = recurrences.splitRecurrence(todo.rec);
setRecurrenceOptionLabels(recSplit.mul);
recurrencePickerSpinner.value = recSplit.mul;
......
......@@ -6,7 +6,7 @@ import { generateRecurrence } from "./recurrences.mjs";
import { convertDate, isToday, isTomorrow, isPast } from "./date.mjs";
import { show } from "./form.mjs";
import "../../node_modules/marked/marked.min.js";
import { RecExtension } from "./todotxtExtensions.mjs";
import { RecExtension, SugarDueExtension } from "./todotxtExtensions.mjs";
import "../../node_modules/jstodotxt/jsTodoExtensions.js";
import "../../node_modules/jstodotxt/jsTodoTxt.js";
......@@ -113,7 +113,7 @@ function configureTodoTableTemplate(append) {
}
function generateItems(content) {
try {
items = { objects: TodoTxt.parse(content, [ new DueExtension(), new RecExtension(), new HiddenExtension() ]) }
items = { objects: TodoTxt.parse(content, [ new SugarDueExtension(), new RecExtension(), new HiddenExtension() ]) }
items.objects = items.objects.filter(function(item) {
if(!item.text) return false;
return true;
......@@ -449,7 +449,7 @@ function setTodoComplete(todo) {
// in case edit form is open, text has changed and complete button is pressed, we do not fall back to the initial value of todo but instead choose input value
if(modalForm.elements[0].value) todo = modalForm.elements[0].value;
// first convert the string to a todo.txt object
todo = new TodoTxtItem(todo, [ new DueExtension(), new RecExtension(), new HiddenExtension() ]);
todo = new TodoTxtItem(todo, [ new SugarDueExtension(), new RecExtension(), new HiddenExtension() ]);
// get index of todo
const index = items.objects.map(function(item) {return item.toString(); }).indexOf(todo.toString());
// mark item as in progress
......@@ -492,7 +492,7 @@ function setTodoDelete(todo) {
// in case edit form is open, text has changed and complete button is pressed, we do not fall back to the initial value of todo but instead choose input value
if(modalForm.elements[0].value) todo = modalForm.elements[0].value;
// first convert the string to a todo.txt object
todo = new TodoTxtItem(todo, [ new DueExtension(), new RecExtension(), new HiddenExtension() ]);
todo = new TodoTxtItem(todo, [ new SugarDueExtension(), new RecExtension(), new HiddenExtension() ]);
// get index of todo
const index = items.objects.map(function(item) {return item.toString(); }).indexOf(todo.toString());
// Delete item
......@@ -533,7 +533,7 @@ async function archiveTodos() {
const getContentFromDoneFile = new Promise(function(resolve) {
window.api.send("getContent", doneFile());
return window.api.receive("getContent", (content) => {
//resolve(TodoTxt.parse(content, [ new DueExtension(), new HiddenExtension(), new RecExtension() ]));
//resolve(TodoTxt.parse(content, [ new SugarDueExtension(), new HiddenExtension(), new RecExtension() ]));
resolve(content);
});
});
......
......@@ -16,3 +16,31 @@ RecExtension.prototype.parsingFunction = function(line) {
};
export { RecExtension };
function SugarDueExtension() {
this.name = "due";
}
SugarDueExtension.prototype = new TodoTxtExtension();
SugarDueExtension.prototype.parsingFunction = function (line) {
var dueDate = null;
var indexDueKeyword = line.indexOf("due:");
// Find keyword due
if (indexDueKeyword >= 0) {
var stringAfterDue = line.substr(indexDueKeyword + 4)
var words = stringAfterDue.split(" ");
var match = null;
// Try to parse a valid date until the end of the text
for (var i = Math.max(5, words.length); i > 0; i--) {
match = words.slice(0, i).join(" ");
dueDate = Sugar.Date.create(match);
if (Sugar.Date.isValid(dueDate)) {
return [dueDate, line.replace("due:" + match, ''), Sugar.Date.format(dueDate, '%Y-%m-%d')];
}
}
}
return [null, null, null];
};
export { SugarDueExtension };
\ No newline at end of file
......@@ -115,7 +115,7 @@ function configureMainView() {
noResultContainer.classList.add("is-active");
return Promise.resolve("Info: No results");
// TODO explain
} else if(userData.file && todos.visibleRows>0 && todos.items.filtered.length>0) {
} else if(userData.file && todos.items.filtered.length>0) {
todoTableSearchContainer.classList.add("is-active");
addTodoContainer.classList.remove("is-active");
noResultContainer.classList.remove("is-active");
......
......@@ -680,7 +680,7 @@ nav {
}
#autoCompleteContainer {
height: auto;
position: absolute;
position: fixed;
display: none;
z-index: 50;
opacity: .90;
......
......@@ -6135,6 +6135,18 @@ stylelint@^13.13.1:
v8-compile-cache "^2.3.0"
write-file-atomic "^3.0.3"
sugar-core@^2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/sugar-core/-/sugar-core-2.0.6.tgz#785e0cd64aa7302ea54d47bc1213efe52c006270"
integrity sha512-YmLFysR3Si6RImqL1+aB6JH81EXxvXn5iXhPf2PsjfoUYEwCxFDYCQY+zC3WqviuGWzxFaSkkJvkUE05Y03L5Q==
sugar@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/sugar/-/sugar-2.0.6.tgz#aa08e389add27109fb35718598313e0503a4fc39"
integrity sha512-s0P2/pjJtAD9VA44+2Gqm3NdC4v+08melA6YubOxzshu628krTbn95/M2GWMrI9rYspZMpYBIrChR46fjQ7xsQ==
dependencies:
sugar-core "^2.0.0"
sugarss@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment