Files
erikandClaude Sonnet 5 6ba93b2a92 Plan Checker, Wochenbericht-Fokus-Modus und Wandtafel-Dashboard-Feinschliff
Plan Checker (neues Studenten-Werkzeug, Seitenleiste + Admin → Plan-Checker-
Merkmale): lädt einen Bauplan als PDF hoch und lässt ChatGPT oder Claude ihn
gegen eine gepflegte Merkmalsliste prüfen. PDF-Rendering (pdf.js +
@napi-rs/canvas) berücksichtigt jetzt Ebenen (Optional Content Groups) und
WASM-Decoder korrekt, sendet eine Übersicht plus mehrere hochaufgelöste
Kachel-Ausschnitte statt eines einzelnen, zu groben Bilds, und beide Anbieter
bekommen dasselbe Bildmaterial. Jedes Prüfergebnis kann auf eines der
gesendeten Bilder als Beleg verweisen (bildIndex) — als Screenshot im
Ergebnis anklickbar, öffnet vergrößert in einem Modal statt eines von Chrome
blockierten target=_blank-Links auf eine data:-URL. KI-Modellname ist jetzt
pro Konto überschreibbar (Einstellungen → Mein Konto) für Drittanbieter mit
abweichendem Modell-Katalog.

Wochenbericht: dichte Kartenansicht bekommt einen Fokus-Modus — nur der
Kommentar liegt offen, Leistungsphasen und Beteiligte stecken in
aufklappbaren Abschnitten statt dauerhaft nebeneinander zu stehen.

Projektbereich: Terminplan-Auslastung rechnet jetzt exakt wie der
Auslastungs-Tab/die Wandtafel (statt einer unabhängigen, tageweisen
Schätzung); die LPH-Fortschrittsliste im Dashboard zeigt nur noch die
beauftragten Leistungsphasen.

Wandtafel: Dashboard-Kopf mit Kennzahlen-Reihe und Panel-Reihe neu
strukturiert, in den Blautönen des Firmenlogos; dieselbe Palette wurde
anschließend auf den gesamten App-weiten Dark Mode übernommen.

Dateibereinigung: nicht mehr benötigte Test-Uploads/Backups entfernt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 14:01:41 +02:00

212 lines
4.6 KiB
JavaScript

'use strict'
const { parseSetCookie } = require('./parse')
const { stringify } = require('./util')
const { webidl } = require('../webidl')
const globalHeadersBrandCheck = (arg) => webidl.brandCheck(arg, webidl.util.MakeTypeAssertion(globalThis.Headers))
const undiciHeadersBrandCheck = (arg) => webidl.brandCheck(arg, webidl.is.Headers)
function brandCheckHeaders (arg) {
try {
undiciHeadersBrandCheck(arg)
return
} catch {
}
globalHeadersBrandCheck(arg)
}
/**
* @typedef {Object} Cookie
* @property {string} name
* @property {string} value
* @property {Date|number} [expires]
* @property {number} [maxAge]
* @property {string} [domain]
* @property {string} [path]
* @property {boolean} [secure]
* @property {boolean} [httpOnly]
* @property {'Strict'|'Lax'|'None'} [sameSite]
* @property {string[]} [unparsed]
*/
/**
* @param {Headers} headers
* @returns {Record<string, string>}
*/
function getCookies (headers) {
webidl.argumentLengthCheck(arguments, 1, 'getCookies')
brandCheckHeaders(headers)
const cookie = headers.get('cookie')
// A null prototype keeps a cookie named `__proto__` from hitting the
// Object.prototype setter, which would silently drop it.
/** @type {Record<string, string>} */
const out = { __proto__: null }
if (!cookie) {
return out
}
for (const piece of cookie.split(';')) {
const [name, ...value] = piece.split('=')
out[name.trim()] = value.join('=')
}
return out
}
/**
* @param {Headers} headers
* @param {string} name
* @param {{ path?: string, domain?: string }|undefined} attributes
* @returns {void}
*/
function deleteCookie (headers, name, attributes) {
brandCheckHeaders(headers)
const prefix = 'deleteCookie'
webidl.argumentLengthCheck(arguments, 2, prefix)
name = webidl.converters.DOMString(name, prefix, 'name')
attributes = webidl.converters.DeleteCookieAttributes(attributes)
// Matches behavior of
// https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278
setCookie(headers, {
name,
value: '',
expires: new Date(0),
...attributes
})
}
/**
* @param {Headers} headers
* @returns {Cookie[]}
*/
function getSetCookies (headers) {
webidl.argumentLengthCheck(arguments, 1, 'getSetCookies')
brandCheckHeaders(headers)
const cookies = headers.getSetCookie()
if (!cookies) {
return []
}
return cookies.map((pair) => parseSetCookie(pair))
}
/**
* Parses a cookie string
* @param {string} cookie
*/
function parseCookie (cookie) {
cookie = webidl.converters.DOMString(cookie)
return parseSetCookie(cookie)
}
/**
* @param {Headers} headers
* @param {Cookie} cookie
* @returns {void}
*/
function setCookie (headers, cookie) {
webidl.argumentLengthCheck(arguments, 2, 'setCookie')
brandCheckHeaders(headers)
cookie = webidl.converters.Cookie(cookie)
const str = stringify(cookie)
if (str) {
headers.append('set-cookie', str, true)
}
}
webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([
{
converter: webidl.nullableConverter(webidl.converters.DOMString),
key: 'path',
defaultValue: () => null
},
{
converter: webidl.nullableConverter(webidl.converters.DOMString),
key: 'domain',
defaultValue: () => null
}
])
webidl.converters.Cookie = webidl.dictionaryConverter([
{
converter: webidl.converters.DOMString,
key: 'name'
},
{
converter: webidl.converters.DOMString,
key: 'value'
},
{
converter: webidl.nullableConverter((value) => {
if (typeof value === 'number') {
return webidl.converters['unsigned long long'](value)
}
return new Date(value)
}),
key: 'expires',
defaultValue: () => null
},
{
converter: webidl.nullableConverter(webidl.converters['long long']),
key: 'maxAge',
defaultValue: () => null
},
{
converter: webidl.nullableConverter(webidl.converters.DOMString),
key: 'domain',
defaultValue: () => null
},
{
converter: webidl.nullableConverter(webidl.converters.DOMString),
key: 'path',
defaultValue: () => null
},
{
converter: webidl.nullableConverter(webidl.converters.boolean),
key: 'secure',
defaultValue: () => null
},
{
converter: webidl.nullableConverter(webidl.converters.boolean),
key: 'httpOnly',
defaultValue: () => null
},
{
converter: webidl.converters.USVString,
key: 'sameSite',
allowedValues: ['Strict', 'Lax', 'None']
},
{
converter: webidl.sequenceConverter(webidl.converters.DOMString),
key: 'unparsed',
defaultValue: () => []
}
])
module.exports = {
getCookies,
deleteCookie,
getSetCookies,
setCookie,
parseCookie
}