Compare commits

...

4 Commits

Author SHA1 Message Date
ed26f6e46f Merge branch 'bugfix/wiki-css'
All checks were successful
Build Docker Image / Docker-Build (push) Successful in 2m6s
Build Docker Image / Clean-Registry (push) Successful in -11s
2026-03-31 00:16:37 +02:00
c04dfe225c improved spreadsheet editing
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-03-30 23:59:00 +02:00
eb4a983d11 fixed bug: now redirecting to newest version when deleting wiki page
All checks were successful
Build Docker Image / Docker-Build (push) Successful in 2m1s
Build Docker Image / Clean-Registry (push) Successful in -12s
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-03-30 21:07:55 +02:00
d7c32ef69a added company selector to project form
All checks were successful
Build Docker Image / Docker-Build (push) Successful in 2m5s
Build Docker Image / Clean-Registry (push) Successful in -11s
2026-03-26 12:37:25 +01:00
8 changed files with 68 additions and 21 deletions

View File

@@ -17,7 +17,7 @@ public class Project implements Mappable {
private final Map<Long,Member> members; private final Map<Long,Member> members;
private final Collection<Status> allowedStates; private final Collection<Status> allowedStates;
private boolean showClosed; private boolean showClosed;
private final Long companyId; private Long companyId;
private int status; private int status;
private String name; private String name;
private final long id; private final long id;
@@ -96,6 +96,7 @@ public class Project implements Mappable {
public Project patch(JSONObject json) { public Project patch(JSONObject json) {
for (var key : json.keySet()){ for (var key : json.keySet()){
switch (key){ switch (key){
case COMPANY_ID: companyId = json.getLong(COMPANY_ID); break;
case DESCRIPTION: description = json.getString(key); break; case DESCRIPTION: description = json.getString(key); break;
case NAME: name = json.getString(key); break; case NAME: name = json.getString(key); break;
case SHOW_CLOSED: showClosed = json.getBoolean(SHOW_CLOSED); break; case SHOW_CLOSED: showClosed = json.getBoolean(SHOW_CLOSED); break;

View File

@@ -2,7 +2,15 @@
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { t } from '../translations.svelte'; import { t } from '../translations.svelte';
let { classes='markdown', markdown=$bindable({source:'',rendered:''}), onclick = null, oncontextmenu = null, title='', wrapper = 'div' } = $props(); let {
classes='markdown',
markdown=$bindable({source:'',rendered:''}),
onclick = null,
oncontextmenu = e => {},
sheet = null,
title='',
wrapper = 'div'
} = $props();
let jspreadsheet = null; let jspreadsheet = null;
const regex = /@startsheet[\s\S]*?@endsheet/g; const regex = /@startsheet[\s\S]*?@endsheet/g;
const number = /^[0-9.-]+$/ const number = /^[0-9.-]+$/
@@ -31,16 +39,16 @@
if (!markdown.rendered) return; if (!markdown.rendered) return;
let sheets = document.getElementsByClassName('spreadsheet'); let sheets = document.getElementsByClassName('spreadsheet');
for (let i = 0; i < sheets.length; i++) { for (let i = 0; i < sheets.length; i++) {
let sheet = sheets[i]; let current_sheet = sheets[i];
let raw = sheet.innerHTML.trim(); let raw = current_sheet.innerHTML.trim();
if (!jspreadsheet) { if (!jspreadsheet) {
sheet.innerHTML = t('Loading spreadsheet library…'); current_sheet.innerHTML = t('Loading spreadsheet library…');
let module = await import('jspreadsheet-ce'); // path or package name let module = await import('jspreadsheet-ce'); // path or package name
await import('jspreadsheet-ce/dist/jspreadsheet.css'); await import('jspreadsheet-ce/dist/jspreadsheet.css');
jspreadsheet = module.default ?? module; jspreadsheet = module.default ?? module;
} }
if (!jspreadsheet) break; // break loop if library fails to load if (!jspreadsheet) break; // break loop if library fails to load
sheet.innerHTML = t('Processing spreadsheet data…'); current_sheet.innerHTML = t('Processing spreadsheet data…');
// Use parseCSV from the helpers // Use parseCSV from the helpers
@@ -60,14 +68,25 @@
render: formatCell, render: formatCell,
width:`${len}0px` width:`${len}0px`
}}); }});
var w = window.innerWidth;
if (classes == 'preview') w = w/2;
let config = { let config = {
worksheets : [{ worksheets : [{
data:parsed, data:parsed,
columns columns,
tableOverflow: true,
tableWidth: `${w}px`,
}], }],
onchange : (instance, cell, x, y, value) => update(instance, i) onchange : (instance, cell, x, y, value) => update(instance, i),
oneditionstart : (instance, cell, x, y) => oncontextmenu({sheet:current_sheet.id, x,y})
}; };
let wb = jspreadsheet(document.getElementById(sheet.id), config); let wb = jspreadsheet(document.getElementById(current_sheet.id), config);
if (sheet && sheet.sheet == current_sheet.id) {
let cell = wb[0].getCellFromCoords(sheet.x, sheet.y);
cell.scrollIntoView({block:'center'});
wb[0].updateSelectionFromCoords(sheet.x, sheet.y);
wb[0].openEditor(cell);
}
} }
} }

View File

@@ -20,6 +20,7 @@
let start = 0; let start = 0;
let stored_source = $state(store_id ? localStorage.getItem(store_id) : null); let stored_source = $state(store_id ? localStorage.getItem(store_id) : null);
let timer = null; let timer = null;
let sheet = null;
async function applyEdit(){ async function applyEdit(){
let success = await onSet(editValue.source); let success = await onSet(editValue.source);
@@ -80,10 +81,13 @@
function oncontextmenu(evt){ function oncontextmenu(evt){
evt.preventDefault(); if (evt.target) {
evt.stopPropagation(); evt.preventDefault();
startEdit(); evt.stopPropagation();
return false; }
sheet = evt.sheet ? evt : null; // store position of activated cell to focus after editing starts
startEdit();
return false;
} }
function onmousedown(evt){ function onmousedown(evt){
@@ -96,6 +100,10 @@
measured(evt, evt.timeStamp - start); measured(evt, evt.timeStamp - start);
} }
function onresize(evt){
console.log('onresize()',evt);
}
function ontouchstart(evt){ function ontouchstart(evt){
evt.preventDefault(); evt.preventDefault();
start = evt.timeStamp; start = evt.timeStamp;
@@ -137,8 +145,8 @@
{#if stored_source} {#if stored_source}
<span id="restore_markdown" onclick={restore} class="hint">{t('unsaved_content')}</span> <span id="restore_markdown" onclick={restore} class="hint">{t('unsaved_content')}</span>
{/if} {/if}
<textarea bind:value={editValue.source} onkeyup={typed} autofocus={!simple}></textarea> <textarea bind:value={editValue.source} onkeyup={typed} onresize={onresize} data="test" autofocus={!simple}></textarea>
<Display classes="preview" bind:markdown={editValue} /> <Display classes="preview" bind:markdown={editValue} sheet={sheet} />
{#if !simple} {#if !simple}
<div class="buttons"> <div class="buttons">
<button class="cancel" onclick={e => editing = false}>{t('cancel')}</button> <button class="cancel" onclick={e => editing = false}>{t('cancel')}</button>
@@ -146,6 +154,6 @@
</div> </div>
{/if} {/if}
{:else} {:else}
<Display classes={{editable}} markdown={value} {onclick} {oncontextmenu} title={t('right_click_to_edit')} wrapper={type} /> <Display classes={{editable}} markdown={value} {onclick} {oncontextmenu} title={t('right_click_to_edit')} wrapper={type} />
{/if} {/if}
</div> </div>

View File

@@ -6,6 +6,7 @@
import { error, yikes } from '../../warn.svelte'; import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte'; import { t } from '../../translations.svelte';
import CompanySelector from '../../Components/CompanySelector.svelte';
import LineEditor from '../../Components/LineEditor.svelte'; import LineEditor from '../../Components/LineEditor.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte'; import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import PermissionEditor from '../../Components/PermissionEditor.svelte'; import PermissionEditor from '../../Components/PermissionEditor.svelte';
@@ -211,6 +212,11 @@
{#if project.company} {#if project.company}
<div>{t('company')}</div> <div>{t('company')}</div>
<div class="company">{project.company.name}</div> <div class="company">{project.company.name}</div>
{:else}
{#if showSettings}
<div>{t('company')}</div>
<span><CompanySelector caption={t('select_company')} onselect={c => update({company_id:c.id})} /></span>
{/if}
{/if} {/if}
<div>{t('context')}</div> <div>{t('context')}</div>
<div> <div>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router'; import { useTinyRouter } from 'svelte-tiny-router';
import { api, eventStream } from '../../urls.svelte'; import { api, eventStream, get } from '../../urls.svelte';
import { error, yikes } from '../../warn.svelte'; import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte'; import { t } from '../../translations.svelte';
import { user } from '../../user.svelte'; import { user } from '../../user.svelte';
@@ -48,8 +48,13 @@
}); });
if (res.ok){ if (res.ok){
let json = await res.json(); let json = await res.json();
router.navigate(`/wiki/${page.id}/view`); let target = `/wiki/${page.id}/view`;
yikes(); if (window.location.pathname == target) {
loadPage();
} else {
router.navigate(target);
yikes();
}
} else { } else {
error(res); error(res);
} }
@@ -100,7 +105,7 @@
let path = `wiki/page/${key}`; let path = `wiki/page/${key}`;
if (version) path += `/version/${version}`; if (version) path += `/version/${version}`;
const url = api(path); const url = api(path);
const res = await fetch(url,{credentials:'include'}); const res = await get(url);
loadJson(res); loadJson(res);
} }

View File

@@ -251,7 +251,7 @@ CREATE TABLE IF NOT EXISTS {0} (
} }
if (prj.isDirty()){ if (prj.isDirty()){
update(TABLE_PROJECTS).set(NAME, DESCRIPTION, STATUS, COMPANY_ID, SHOW_CLOSED).where(ID,equal(prj.id())).prepare(db) update(TABLE_PROJECTS).set(NAME, DESCRIPTION, STATUS, COMPANY_ID, SHOW_CLOSED).where(ID,equal(prj.id())).prepare(db)
.apply(prj.name(),prj.description(),prj.status(),prj.companyId(),prj.showClosed()) .apply(prj.name(),prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed())
.execute(); .execute();
prj.clean(); prj.clean();
} }

View File

@@ -334,6 +334,10 @@ tr:hover .taglist .tag button {
border-left: 1px solid #333 !important; border-left: 1px solid #333 !important;
} }
.jss_worksheet tr:hover td input{
color: yellow;
}
@media screen and (max-width: 900px) { @media screen and (max-width: 900px) {
#app nav a{ #app nav a{
background: black; background: black;

View File

@@ -325,6 +325,10 @@ tr:hover .taglist .tag button {
border-left: 1px solid #333 !important; border-left: 1px solid #333 !important;
} }
.jss_worksheet tr:hover td input{
color: black;
}
@media screen and (max-width: 900px) { @media screen and (max-width: 900px) {
#app nav a{ #app nav a{
background: black; background: black;