/* Deterministic comment commands. Parsing never performs an action. */ (function (root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; if (root) root.CaseCommands = api; })(typeof window !== 'undefined' ? window : globalThis, function () { 'use strict'; const names = ['tid','tildel','forventet','undersag','mig','todo','afventer','løsning','deadline','udsat','fil','hjælp','timer','relation','prioritet','gruppe','mail','note']; function tokens(value) { const result = []; let i = 0; while (i < value.length) { if (/\s/.test(value[i])) { i++; continue; } if (value[i] === '"') { let text = ''; let closed = false; i++; while (i < value.length) { if (value[i] === '\\' && ['"','\\'].includes(value[i + 1])) { text += value[i + 1]; i += 2; } else if (value[i] === '"') { i++; closed = true; break; } else text += value[i++]; } if (!closed) throw new Error('Afslut teksten med et dobbelt anførselstegn.'); if (i < value.length && !/\s/.test(value[i])) throw new Error('Der skal være mellemrum efter den citerede tekst.'); result.push({value:text,quoted:true}); } else { const start = i; while (i < value.length && !/\s/.test(value[i])) i++; const text = value.slice(start,i); if (text.includes('"')) throw new Error('Sæt hele teksten i dobbelte anførselstegn.'); result.push({value:text,quoted:false}); } } return result; } function duration(text) { const m = /^(\d+(?:[.,]\d+)?)(m|min|h|t)?$/i.exec(text || ''); if (!m) throw new Error('Angiv tid som 30m, 1h eller et antal minutter.'); const value = Number(m[1].replace(',','.')) * (/^[ht]$/i.test(m[2] || '') ? 60 : 1); if (!Number.isInteger(value) || value <= 0 || value > 525600) throw new Error('Tiden skal være et positivt, helt antal minutter.'); return value; } function iso(date) { return [date.getFullYear(),String(date.getMonth()+1).padStart(2,'0'),String(date.getDate()).padStart(2,'0')].join('-'); } function dateValue(text, now) { const value = text.toLowerCase().trim(); if (/^\d{4}-\d{2}-\d{2}$/.test(value)) { const parts = value.split('-').map(Number); const d = new Date(parts[0],parts[1]-1,parts[2]); if (iso(d) !== value) throw new Error('Datoen findes ikke.'); return value; } const base = now instanceof Date ? new Date(now.getTime()) : now ? new Date(now) : new Date(); if (Number.isNaN(base.getTime())) throw new Error('Startdatoen er ugyldig.'); base.setHours(12,0,0,0); if (value === 'i dag' || value === 'idag') return iso(base); if (value === 'i morgen' || value === 'imorgen') { base.setDate(base.getDate()+1); return iso(base); } const weekdays = ['søndag','mandag','tirsdag','onsdag','torsdag','fredag','lørdag']; const day = weekdays.indexOf(value); if (day !== -1) { base.setDate(base.getDate()+(day-base.getDay()+7)%7); return iso(base); } throw new Error('Angiv en dato som 2026-10-09, i morgen eller fredag.'); } function parse(text, options) { const commands = [], errors = [], comment = []; let scope = null; let subcaseCount = 0; options = options || {}; String(text || '').split(/\r?\n/).forEach(function (raw,index) { const line = raw.trim(); if (!line.startsWith('/')) { comment.push(raw); return; } const match = /^(\/\/|\/)([^\s/]+)(?:\s+(.*))?$/.exec(line); const name = match ? match[2].toLowerCase() : ''; try { if (!match || !names.includes(name)) throw new Error(name === 'tildek' ? 'Mente du /tildel?' : 'Ukendt kommando. Skriv /hjælp for at se mulighederne.'); if (match[1] === '//' && scope === null) throw new Error('// kræver en /undersag ovenover.'); const args = tokens(match[3] || ''); const values = args.map(t => t.value); const cmd = {type:'',scope:match[1] === '//' ? scope : 'main',line:index+1}; function count(n) { if (args.length !== n) throw new Error('Kommandoen har manglende eller ekstra oplysninger.'); } function quoted(i) { if (!args[i] || !args[i].quoted || !args[i].value.trim()) throw new Error('Skriv teksten i dobbelte anførselstegn.'); return args[i].value.trim(); } function identifier(i) { const value = args[i] ? values[i].trim() : ''; const pattern = args[i] && args[i].quoted ? /^@?[\p{L}\p{N}_.-]+(?:\s+[\p{L}\p{N}_.-]+)*$/u : /^@?[\p{L}\p{N}_.-]+$/u; if (!value || !pattern.test(value)) throw new Error('Angiv initialer eller et fuldt navn i dobbelte anførselstegn.'); return value.replace(/^@/,'').replace(/\s+/g,' '); } switch (name) { case 'undersag': count(1); if(match[1] === '//') throw new Error('Opret en undersag med /undersag, ikke //undersag.'); cmd.type='subcase'; cmd.title=quoted(0); cmd.scope=subcaseCount++; scope=cmd.scope; break; case 'tid': count(3); cmd.type='time'; cmd.minutes=duration(values[0]); cmd.category=identifier(1); cmd.description=quoted(2); break; case 'tildel': count(2); cmd.type='assign'; cmd.assignee=identifier(0); cmd.reason=quoted(1); break; case 'forventet': count(1); cmd.type='estimate'; cmd.minutes=duration(values[0]); break; case 'mig': count(0); cmd.type='self'; break; case 'todo': { cmd.type='todo'; cmd.title=quoted(0); let rest=values.slice(1); if (rest[0] && rest[0].startsWith('@')) { cmd.assignee=identifier(1); rest=rest.slice(1); } if(rest.length) { if (args.slice(values.length-rest.length).some(t=>t.quoted)) throw new Error('Todo-fristen skal skrives uden anførselstegn.'); cmd.date=dateValue(rest.join(' '),options.now); } break; } case 'afventer': count(2); if(values[0].toLowerCase() !== 'kunde') throw new Error('Brug /afventer kunde "Begrundelse".'); cmd.type='waiting'; cmd.reason=quoted(1); break; case 'løsning': case 'note': case 'mail': count(1); cmd.type=name === 'løsning' ? 'solution' : name; cmd.text=quoted(0); break; case 'deadline': case 'udsat': if(!args.length || args.some(t=>t.quoted)) throw new Error('Angiv en dato efter kommandoen.'); cmd.type=name === 'udsat' ? 'defer' : 'deadline'; cmd.date=dateValue(values.join(' '),options.now); break; case 'fil': case 'hjælp': count(0); cmd.type=name === 'fil' ? 'file' : 'help'; break; case 'timer': cmd.type='timer'; cmd.action=(values[0] || '').toLowerCase(); if(cmd.action === 'start') { count(2); cmd.description=quoted(1); } else if(cmd.action === 'pause') count(1); else throw new Error('Brug /timer start "Beskrivelse" eller /timer pause.'); break; case 'relation': count(1); if(!/^#?[1-9]\d*$/.test(values[0]) || !Number.isSafeInteger(Number(values[0].replace('#','')))) throw new Error('Angiv et sagsnummer som #1248.'); cmd.type='relation'; cmd.caseId=Number(values[0].replace('#','')); break; case 'prioritet': count(2); cmd.type='priority'; cmd.priority=identifier(0).toLowerCase(); if(!['lav','normal','høj','kritisk'].includes(cmd.priority)) throw new Error('Prioritet skal være lav, normal, høj eller kritisk.'); cmd.reason=quoted(1); break; case 'gruppe': count(2); cmd.type='group'; cmd.group=identifier(0); cmd.reason=quoted(1); break; } commands.push(cmd); } catch(error) { errors.push({line:index+1,command:name,message:error.message}); } }); return {comment:comment.join('\n').trim(),commands:commands,errors:errors}; } return {parse:parse,duration:duration,resolveDate:dateValue,names:names}; });