major improvement to easylist for usability on mobile devices
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -20,11 +20,23 @@
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
|
||||
function byName(a,b){
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
function extend(e,task){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
highlight = task;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getTask(evt){
|
||||
var link = evt.target;
|
||||
var id = link.getAttribute('task_id');
|
||||
return tasks[id];
|
||||
}
|
||||
|
||||
function goTag(e,newTag){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -33,6 +45,12 @@
|
||||
load();
|
||||
}
|
||||
|
||||
function ignore(evt){
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
async function load(){
|
||||
const url = api(`task/tagged/${tag}`);
|
||||
const res = await get(url);
|
||||
@@ -43,57 +61,17 @@
|
||||
} else error(res);
|
||||
}
|
||||
|
||||
function noNoIndex(task){
|
||||
return !task.no_index;
|
||||
}
|
||||
|
||||
function ignore(evt){
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
function match(task){
|
||||
if (!search) return true;
|
||||
if (task.name.toLowerCase().includes(search)) return true;
|
||||
if (task.tags){
|
||||
for (let tag of task.tags){
|
||||
if (tag.toLowerCase().includes(search)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function extend(e,task){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
highlight = task;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function getTask(evt){
|
||||
var link = evt.target;
|
||||
var id = link.getAttribute('task_id');
|
||||
return tasks[id];
|
||||
}
|
||||
|
||||
function onclick(evt) {
|
||||
let task = getTask(evt);
|
||||
if (task.status <= 20) { // open
|
||||
update(task,60);
|
||||
} else update(task,20);
|
||||
return ignore(evt);
|
||||
}
|
||||
|
||||
function oncontextmenu(evt) {
|
||||
highlight = getTask(evt);
|
||||
return ignore(evt);
|
||||
}
|
||||
|
||||
function ontouchstart(evt){
|
||||
start = evt.timeStamp;
|
||||
x = evt.touches[0].clientX;
|
||||
y = evt.touches[0].clientY;
|
||||
return ignore(evt);
|
||||
}
|
||||
|
||||
function ontouchend(evt){
|
||||
let d = Math.abs(x - evt.changedTouches[0].clientX) + Math.abs(y - evt.changedTouches[0].clientY);
|
||||
measured(evt, evt.timeStamp - start, d);
|
||||
return ignore(evt);
|
||||
}
|
||||
|
||||
|
||||
function measured(evt,duration,d){
|
||||
if (d > 100) return;
|
||||
if (duration < 500){
|
||||
@@ -103,6 +81,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
function noNoIndex(task){
|
||||
return !task.no_index;
|
||||
}
|
||||
|
||||
function onclick(evt) {
|
||||
ignore(evt);
|
||||
let task = getTask(evt);
|
||||
if (task.status <= 20) { // open
|
||||
update(task,60);
|
||||
} else update(task,20);
|
||||
return false;
|
||||
}
|
||||
|
||||
function oncontextmenu(evt) {
|
||||
ignore(evt);
|
||||
highlight = getTask(evt);
|
||||
return false;
|
||||
}
|
||||
|
||||
function ontouchstart(evt){
|
||||
ignore(evt);
|
||||
start = evt.timeStamp;
|
||||
x = evt.touches[0].clientX;
|
||||
y = evt.touches[0].clientY;
|
||||
return false;
|
||||
}
|
||||
|
||||
function ontouchend(evt){
|
||||
ignore(evt);
|
||||
let d = Math.abs(x - evt.changedTouches[0].clientX) + Math.abs(y - evt.changedTouches[0].clientY);
|
||||
measured(evt, evt.timeStamp - start, d);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
async function update(task,newState){
|
||||
highlight = null;
|
||||
const url = api(`task/${task.id}`);
|
||||
@@ -111,25 +124,23 @@
|
||||
task.status = newState;
|
||||
yikes();
|
||||
// filter = null; // not sure what is better, resetting or keeping
|
||||
input.focus();
|
||||
|
||||
} else error(res);
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<h2>{tag}</h2>
|
||||
<h2>{t('tasks_for_tag',{tag:decodeURI(tag)})}</h2>
|
||||
|
||||
<div class="easylist">
|
||||
<fieldset class="open">
|
||||
<legend>{t('state_open')}</legend>
|
||||
{#if sorted}
|
||||
{#each sorted as task}
|
||||
{#if task.status == 20 && (!filter || task.name.toLowerCase().includes(search))}
|
||||
<a href={`/task/${task.id}/view`} title={task.description.source} task_id={task.id} {onclick} {oncontextmenu} {ontouchstart} {ontouchend} >
|
||||
{#if task.status == 20 && match(task)}
|
||||
<div href={`/task/${task.id}/view`} title={task.description.source} task_id={task.id} {onclick} {oncontextmenu} {ontouchstart} {ontouchend} onmousedown={ontouchstart} onmouseup={ontouchend} >
|
||||
{task.name}
|
||||
</a>
|
||||
</div>
|
||||
{#if highlight == task}
|
||||
<Detail task={task} goTag={goTag} />
|
||||
{/if}
|
||||
@@ -142,10 +153,10 @@
|
||||
<legend>{t('state_complete')}</legend>
|
||||
{#if sorted}
|
||||
{#each sorted as task}
|
||||
{#if task.status > 20 && (!filter || task.name.toLowerCase().includes(search))}
|
||||
<a href={`/task/${task.id}/view`} title={task.description.source} task_id={task.id} {onclick} {oncontextmenu} {ontouchstart} {ontouchend} >
|
||||
{#if task.status > 20 && match(task)}
|
||||
<div href={`/task/${task.id}/view`} title={task.description.source} task_id={task.id} {onclick} {oncontextmenu} {ontouchstart} {ontouchend} onmousedown={ontouchstart} onmouseup={ontouchend} >
|
||||
{task.name}
|
||||
</a>
|
||||
</div>
|
||||
{#if highlight == task}
|
||||
<Detail task={task} goTag={goTag} />
|
||||
{/if}
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { api, get, patch } from '../../urls.svelte';
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
import { t } from '../../translations.svelte';
|
||||
|
||||
let { goTag, task } = $props();
|
||||
let router = useTinyRouter();
|
||||
|
||||
async function load(){
|
||||
const url = api(`tags/task/${task.id}`);
|
||||
const res = await get(url);
|
||||
if (res.ok){
|
||||
yikes();
|
||||
task.tags = await res.json();
|
||||
} else error(res);
|
||||
function onclick(){
|
||||
router.navigate(`/task/${task.id}/edit`);
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<button class="edit" {onclick}>{t('edit')}</button>
|
||||
{@html task.description.rendered}
|
||||
{#if task.tags}
|
||||
{t('other_tags')}:
|
||||
{t('other_tags')}:<br/>
|
||||
{#each task.tags as tag}
|
||||
<button onclick={e => goTag(e,tag)}>{tag}</button>
|
||||
{/each}
|
||||
|
||||
@@ -14,6 +14,8 @@ import static de.srsoftware.umbrella.core.model.Permission.OWNER;
|
||||
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
||||
import static de.srsoftware.umbrella.task.Constants.*;
|
||||
import static java.lang.System.Logger.Level.WARNING;
|
||||
import static java.net.URLDecoder.decode;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import de.srsoftware.configuration.Configuration;
|
||||
@@ -180,11 +182,16 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
}
|
||||
|
||||
private boolean getTaggedTasks(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||
var tag = path.toString();
|
||||
var tag = decode(path.toString(), UTF_8);
|
||||
var tags = tagService().getTagUses(user,tag);
|
||||
var taskIds = nullable(tags.get(TASK)).orElseGet(ArrayList::new);
|
||||
var tasks = taskDb.load(taskIds);
|
||||
return sendContent(ex, mapValues(tasks));
|
||||
var tasks = mapValues(taskDb.load(taskIds));
|
||||
var taskTags = tagService().getTags(TASK,taskIds,user);
|
||||
for (var entry : tasks.entrySet()){
|
||||
var list = taskTags.get(entry.getKey());
|
||||
entry.getValue().put(TAGS,list==null?List.of():list);
|
||||
}
|
||||
return sendContent(ex, tasks);
|
||||
}
|
||||
|
||||
private boolean getTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
||||
|
||||
@@ -191,6 +191,7 @@
|
||||
"oidc_Login" : "Anmeldung mit OIDC",
|
||||
"old_password": "altes Passwort",
|
||||
"organization": "Organisation",
|
||||
"other_tags": "andere Tags",
|
||||
|
||||
"page": "Seite",
|
||||
"parent_task": "übergeordnete Aufgabe",
|
||||
@@ -278,6 +279,7 @@
|
||||
"task": "Aufgabe",
|
||||
"task_list": "Aufgabenliste",
|
||||
"tasks": "Aufgaben",
|
||||
"tasks_for_tag": "Aufgaben mit Tag „{tag}“",
|
||||
"tax_id": "Steuernummer",
|
||||
"TAX-NUMBER": "Steuernummer",
|
||||
"tax_rate": "Steuersatz",
|
||||
|
||||
@@ -191,6 +191,7 @@
|
||||
"oidc_Login" : "Login via OIDC",
|
||||
"old_password": "old password",
|
||||
"organization": "organization",
|
||||
"other_tags": "other tags",
|
||||
|
||||
"page": "page",
|
||||
"parent_task": "parent task",
|
||||
@@ -278,6 +279,7 @@
|
||||
"task": "task",
|
||||
"task_list": "task list",
|
||||
"tasks": "tasks",
|
||||
"tasks_for_tag": "tasks with tag „{tag}“",
|
||||
"tax_id": "tax ID",
|
||||
"TAX-NUMBER": "tax ID",
|
||||
"tax_rate": "tax rate",
|
||||
|
||||
@@ -296,8 +296,8 @@ tr:hover .taglist .tag button {
|
||||
color: #222200;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
border-color:orange;
|
||||
.easylist > fieldset > div {
|
||||
border-color: orange;
|
||||
color: orange;
|
||||
}
|
||||
.easylist fieldset {
|
||||
|
||||
@@ -407,20 +407,25 @@ a.wikilink{
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
display: block;
|
||||
border: 1px solid;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.easylist .filter{
|
||||
position: sticky;
|
||||
top: 40px;
|
||||
bottom: 22px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.easylist .edit{
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.grid2{
|
||||
display: grid;
|
||||
@@ -443,15 +448,13 @@ a.wikilink{
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
font-size: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
.easylist input{
|
||||
font-size: 20px;
|
||||
}
|
||||
.easylist .filter{
|
||||
top: 95px;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset.vcard{
|
||||
|
||||
@@ -286,7 +286,7 @@ tr:hover .taglist .tag button {
|
||||
color: #222200;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
border-color: orange;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
@@ -485,13 +485,14 @@ a.wikilink{
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
display: block;
|
||||
border: 1px solid;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.easylist .filter{
|
||||
position: sticky;
|
||||
@@ -499,6 +500,10 @@ a.wikilink{
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.easylist .edit{
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.grid2{
|
||||
display: grid;
|
||||
@@ -521,8 +526,9 @@ a.wikilink{
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
font-size: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
.easylist input{
|
||||
font-size: 20px;
|
||||
|
||||
@@ -274,7 +274,7 @@ tr:hover .taglist .tag button {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
border-color: blue;
|
||||
color: blue;
|
||||
background: #dfe4ff;
|
||||
|
||||
@@ -407,20 +407,25 @@ a.wikilink{
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
display: block;
|
||||
border: 1px solid;
|
||||
margin: 7px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.easylist .filter{
|
||||
position: sticky;
|
||||
top: 40px;
|
||||
bottom: 22px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.easylist .edit{
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.grid2{
|
||||
display: grid;
|
||||
@@ -443,15 +448,13 @@ a.wikilink{
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.easylist a {
|
||||
.easylist > fieldset > div {
|
||||
font-size: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
.easylist input{
|
||||
font-size: 20px;
|
||||
}
|
||||
.easylist .filter{
|
||||
top: 95px;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset.vcard{
|
||||
|
||||
Reference in New Issue
Block a user