From 504ed5c1834123f32e29f86e43cc20ecb04432b1 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 13 Mar 2026 16:36:19 +1100 Subject: [PATCH 1/6] fix: schedulePane render --- src/schedule/schedulePane.js | 52 +++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/schedule/schedulePane.js b/src/schedule/schedulePane.js index 46fe40a6..f9dee359 100644 --- a/src/schedule/schedulePane.js +++ b/src/schedule/schedulePane.js @@ -557,9 +557,53 @@ export const schedulePane = { const showForms = function () { clearElement(naviCenter) // Remove refresh button if nec const div = naviMain + + // form2 depends on sched:allDay; seed a local default for new polls + if (!kb.any(subject, ns.sched('allDay'))) { + kb.add( + subject, + ns.sched('allDay'), + $rdf.literal( + 'true', + undefined, + $rdf.sym('http://www.w3.org/2001/XMLSchema#boolean') + ), + detailsDoc + ) + } + const wizard = true let currentSlide = 0 let gotDoneButton = false + + const hasFormControls = function (container) { + return !!container.querySelector('input, select, textarea, button') + } + + const asBoolean = function (term, fallback) { + if (!term) return fallback + const value = (term.value || '').toLowerCase() + if (value === 'true' || value === '1') return true + if (value === 'false' || value === '0') return false + return fallback + } + + const renderTimeProposalFallback = function (slide) { + const allDayValue = asBoolean(kb.any(subject, ns.sched('allDay')), true) + const fallbackForm = kb.sym( + formsURI + (allDayValue ? '#AllDayForm2' : '#NotAllDayForm2') + ) + UI.widgets.appendForm( + document, + slide, + {}, + subject, + fallbackForm, + detailsDoc, + complainIfBad + ) + } + if (wizard) { const forms = [form1, form2, form3] const slides = [] @@ -575,6 +619,12 @@ export const schedulePane = { detailsDoc, complainIfBad ) + + // Some stores end up with form2's ui:Options unresolved; force a usable input form. + if (f === 1 && !hasFormControls(slide)) { + renderTimeProposalFallback(slide) + } + slides.push(slide) } @@ -882,7 +932,7 @@ export const schedulePane = { // Read or create empty results file function getResults () { - fetcher.nowOrWhenFetched(resultsDoc.uri, (ok, body, response) => { + fetcher.nowOrWhenFetched(resultsDoc.uri, undefined, (ok, body, response) => { if (!ok) { if (response.status === 404) { // / Check explicitly for 404 error From e6ddf2c8b36c2fcc1e01e5b18f7948078415ad8d Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Sun, 15 Mar 2026 19:17:19 +1100 Subject: [PATCH 2/6] added guard --- src/schedule/schedulePane.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/schedule/schedulePane.js b/src/schedule/schedulePane.js index f9dee359..bb49951b 100644 --- a/src/schedule/schedulePane.js +++ b/src/schedule/schedulePane.js @@ -1018,11 +1018,15 @@ export const schedulePane = { options.set_x = kb.each(subject, ns.sched('option')) // @@@@@ option -> dtstart in future options.set_x = options.set_x.map(function (opt) { return kb.any(opt, ns.cal('dtstart')) + }).filter(function (time) { + return !!time }) options.set_y = kb.each(subject, ns.sched('response')) options.set_y = options.set_y.map(function (resp) { return kb.any(resp, ns.dc('author')) + }).filter(function (author) { + return !!author }) const possibleTimes = kb @@ -1030,6 +1034,9 @@ export const schedulePane = { .map(function (opt) { return kb.any(opt, ns.cal('dtstart')) }) + .filter(function (time) { + return !!time + }) const displayTheMatrix = function () { const matrix = div.appendChild( @@ -1105,6 +1112,7 @@ export const schedulePane = { } // @@ may need that const selectOptions = {} const predicate = ns.sched('availabilty') + if (!x) return const cellSubject = dataPointForNT[x.toNT()] const selector = UI.widgets.makeSelectForOptions( dom, @@ -1143,19 +1151,22 @@ export const schedulePane = { const dps = kb.each(myResponse, ns.sched('cell')) dps.forEach(function (dataPoint) { const time = kb.any(dataPoint, ns.cal('dtstart')) + if (!time) return dataPointForNT[time.toNT()] = dataPoint }) } for (let j = 0; j < possibleTimes.length; j++) { - if (dataPointForNT[possibleTimes[j].toNT()]) continue + const possibleTime = possibleTimes[j] + if (!possibleTime) continue + if (dataPointForNT[possibleTime.toNT()]) continue const dataPoint = $rdf.sym(id + '_' + j) insertables.push( $rdf.st(myResponse, ns.sched('cell'), dataPoint, doc) ) insertables.push( - $rdf.st(dataPoint, ns.cal('dtstart'), possibleTimes[j], doc) + $rdf.st(dataPoint, ns.cal('dtstart'), possibleTime, doc) ) // @@ - dataPointForNT[possibleTimes[j].toNT()] = dataPoint + dataPointForNT[possibleTime.toNT()] = dataPoint } if (insertables.length) { kb.updater.update([], insertables, function ( From b353794f36f605d293ca8107ef1c6053895195bc Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Sun, 15 Mar 2026 19:54:33 +1100 Subject: [PATCH 3/6] option for times as well --- src/schedule/schedulePane.js | 98 ++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/src/schedule/schedulePane.js b/src/schedule/schedulePane.js index bb49951b..286c58ed 100644 --- a/src/schedule/schedulePane.js +++ b/src/schedule/schedulePane.js @@ -589,19 +589,95 @@ export const schedulePane = { } const renderTimeProposalFallback = function (slide) { - const allDayValue = asBoolean(kb.any(subject, ns.sched('allDay')), true) - const fallbackForm = kb.sym( - formsURI + (allDayValue ? '#AllDayForm2' : '#NotAllDayForm2') + const controls = slide.appendChild(dom.createElement('div')) + controls.setAttribute('style', 'margin: 0.5em 0;') + + const label = controls.appendChild(dom.createElement('label')) + label.textContent = 'Time proposal mode: ' + + const modeSelector = controls.appendChild(dom.createElement('select')) + modeSelector.setAttribute('style', inputStyle) + + const allDayOption = modeSelector.appendChild(dom.createElement('option')) + allDayOption.setAttribute('value', 'true') + allDayOption.textContent = 'All day' + + const specificTimesOption = modeSelector.appendChild( + dom.createElement('option') ) - UI.widgets.appendForm( - document, - slide, - {}, - subject, - fallbackForm, - detailsDoc, - complainIfBad + specificTimesOption.setAttribute('value', 'false') + specificTimesOption.textContent = 'Specific times' + + const fallbackContainer = slide.appendChild(dom.createElement('div')) + + const renderChosenFallbackForm = function () { + clearElement(fallbackContainer) + const allDayValue = asBoolean(kb.any(subject, ns.sched('allDay')), true) + const fallbackForm = kb.sym( + formsURI + (allDayValue ? '#AllDayForm2' : '#NotAllDayForm2') + ) + UI.widgets.appendForm( + document, + fallbackContainer, + {}, + subject, + fallbackForm, + detailsDoc, + complainIfBad + ) + } + + const setAllDayAndRender = function (nextAllDayValue) { + const existing = kb.statementsMatching( + subject, + ns.sched('allDay'), + undefined, + detailsDoc + ) + const replacement = [ + $rdf.st( + subject, + ns.sched('allDay'), + $rdf.literal( + nextAllDayValue ? 'true' : 'false', + undefined, + $rdf.sym('http://www.w3.org/2001/XMLSchema#boolean') + ), + detailsDoc + ) + ] + + if (kb.updater && kb.updater.editable(detailsDoc.uri, kb)) { + kb.updater.update(existing, replacement, function ( + _uri, + success, + body + ) { + if (!success) { + complainIfBad(false, body) + return + } + renderChosenFallbackForm() + }) + } else { + existing.forEach(st => kb.remove(st)) + replacement.forEach(st => kb.add(st.subject, st.predicate, st.object, st.why)) + renderChosenFallbackForm() + } + } + + modeSelector.value = + asBoolean(kb.any(subject, ns.sched('allDay')), true) ? 'true' : 'false' + + modeSelector.addEventListener( + 'change', + function () { + setAllDayAndRender(modeSelector.value === 'true') + }, + false ) + + renderChosenFallbackForm() } if (wizard) { From a56d4a872e39f118888ee0ae64ef968c82b978dd Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Sun, 15 Mar 2026 20:59:41 +1100 Subject: [PATCH 4/6] revert dropdown onschedule page --- src/schedule/schedulePane.js | 98 ++++-------------------------------- 1 file changed, 11 insertions(+), 87 deletions(-) diff --git a/src/schedule/schedulePane.js b/src/schedule/schedulePane.js index 286c58ed..bb49951b 100644 --- a/src/schedule/schedulePane.js +++ b/src/schedule/schedulePane.js @@ -589,95 +589,19 @@ export const schedulePane = { } const renderTimeProposalFallback = function (slide) { - const controls = slide.appendChild(dom.createElement('div')) - controls.setAttribute('style', 'margin: 0.5em 0;') - - const label = controls.appendChild(dom.createElement('label')) - label.textContent = 'Time proposal mode: ' - - const modeSelector = controls.appendChild(dom.createElement('select')) - modeSelector.setAttribute('style', inputStyle) - - const allDayOption = modeSelector.appendChild(dom.createElement('option')) - allDayOption.setAttribute('value', 'true') - allDayOption.textContent = 'All day' - - const specificTimesOption = modeSelector.appendChild( - dom.createElement('option') + const allDayValue = asBoolean(kb.any(subject, ns.sched('allDay')), true) + const fallbackForm = kb.sym( + formsURI + (allDayValue ? '#AllDayForm2' : '#NotAllDayForm2') ) - specificTimesOption.setAttribute('value', 'false') - specificTimesOption.textContent = 'Specific times' - - const fallbackContainer = slide.appendChild(dom.createElement('div')) - - const renderChosenFallbackForm = function () { - clearElement(fallbackContainer) - const allDayValue = asBoolean(kb.any(subject, ns.sched('allDay')), true) - const fallbackForm = kb.sym( - formsURI + (allDayValue ? '#AllDayForm2' : '#NotAllDayForm2') - ) - UI.widgets.appendForm( - document, - fallbackContainer, - {}, - subject, - fallbackForm, - detailsDoc, - complainIfBad - ) - } - - const setAllDayAndRender = function (nextAllDayValue) { - const existing = kb.statementsMatching( - subject, - ns.sched('allDay'), - undefined, - detailsDoc - ) - const replacement = [ - $rdf.st( - subject, - ns.sched('allDay'), - $rdf.literal( - nextAllDayValue ? 'true' : 'false', - undefined, - $rdf.sym('http://www.w3.org/2001/XMLSchema#boolean') - ), - detailsDoc - ) - ] - - if (kb.updater && kb.updater.editable(detailsDoc.uri, kb)) { - kb.updater.update(existing, replacement, function ( - _uri, - success, - body - ) { - if (!success) { - complainIfBad(false, body) - return - } - renderChosenFallbackForm() - }) - } else { - existing.forEach(st => kb.remove(st)) - replacement.forEach(st => kb.add(st.subject, st.predicate, st.object, st.why)) - renderChosenFallbackForm() - } - } - - modeSelector.value = - asBoolean(kb.any(subject, ns.sched('allDay')), true) ? 'true' : 'false' - - modeSelector.addEventListener( - 'change', - function () { - setAllDayAndRender(modeSelector.value === 'true') - }, - false + UI.widgets.appendForm( + document, + slide, + {}, + subject, + fallbackForm, + detailsDoc, + complainIfBad ) - - renderChosenFallbackForm() } if (wizard) { From 0b1b7c93ed0e0db2e9c6b82dcd663cf80f9683d6 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 20 Mar 2026 16:49:31 +1100 Subject: [PATCH 5/6] if not index use details --- src/schedule/schedulePane.js | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/schedule/schedulePane.js b/src/schedule/schedulePane.js index bb49951b..16aacca8 100644 --- a/src/schedule/schedulePane.js +++ b/src/schedule/schedulePane.js @@ -18,6 +18,23 @@ const possibleAvailabilities = [ ns.sched('Yes') ] +function resolveScheduleSubject (kb, subject) { + if (!subject || !subject.uri) { + return subject + } + + const rewrittenUri = subject.uri.replace( + /index\.ttl#this$/, + 'details.ttl#event' + ) + + if (rewrittenUri === subject.uri) { + return subject + } + + return kb.sym(rewrittenUri) +} + export const schedulePane = { icon: UI.icons.iconBase + 'noun_346777.svg', // @@ better? @@ -27,8 +44,18 @@ export const schedulePane = { // Does the subject deserve an Scheduler pane? label: function (subject, context) { + let t = null const kb = context.session.store - const t = kb.findTypeURIs(subject) + /* Sometimes this pane gets created without an index.ttl#this + file, when useExisting is empty. Only details.ttl#event and + results.ttl get created. folder-pane hardcodes looking for + index.ttl#this */ + t = kb.findTypeURIs(subject) + if (Object.keys(t).length === 0) { + const resolvedSubject = resolveScheduleSubject(kb, subject) + t = kb.findTypeURIs(resolvedSubject) + } + if (t['http://www.w3.org/ns/pim/schedule#SchedulableEvent']) { return 'Scheduling poll' } @@ -321,7 +348,7 @@ export const schedulePane = { const dom = context.dom const kb = context.session.store const ns = UI.ns - const invitation = subject + const invitation = resolveScheduleSubject(kb, subject) const appPathSegment = 'app-when-can-we.w3.org' // how to allocate this string and connect to // //////////////////////////////////////////// @@ -330,8 +357,8 @@ export const schedulePane = { const updater = kb.updater let waitingForLogin = false - const thisInstance = subject - const detailsDoc = subject.doc() + const thisInstance = invitation + const detailsDoc = invitation.doc() const baseDir = detailsDoc.dir() const base = baseDir.uri From 022d123cc1e395d188b96afae9fedfd32ff8d226 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 20 Mar 2026 19:30:21 +1100 Subject: [PATCH 6/6] added index creation on mint --- src/schedule/schedulePane.js | 46 ++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/schedule/schedulePane.js b/src/schedule/schedulePane.js index 16aacca8..064dd7ef 100644 --- a/src/schedule/schedulePane.js +++ b/src/schedule/schedulePane.js @@ -55,7 +55,6 @@ export const schedulePane = { const resolvedSubject = resolveScheduleSubject(kb, subject) t = kb.findTypeURIs(resolvedSubject) } - if (t['http://www.w3.org/ns/pim/schedule#SchedulableEvent']) { return 'Scheduling poll' } @@ -70,8 +69,6 @@ export const schedulePane = { const ns = UI.ns const kb = context.session.store let newBase = options.newBase - const thisInstance = - options.useExisting || $rdf.sym(options.newBase + 'index.ttl#this') const complainIfBad = function (ok, body) { if (ok) return @@ -178,25 +175,19 @@ export const schedulePane = { return } - const base = thisInstance.dir().uri - let newDetailsDoc, newInstance // , newIndexDoc - if (options.useExisting) { - newInstance = options.useExisting - newBase = thisInstance.dir().uri - newDetailsDoc = newInstance.doc() - // newIndexDoc = null if (options.newBase) { throw new Error( 'mint new scheduler: Illegal - have both new base and existing event' ) } - } else { - newDetailsDoc = kb.sym(newBase + 'details.ttl') - // newIndexDoc = kb.sym(newBase + 'index.html') - newInstance = kb.sym(newDetailsDoc.uri + '#event') + newBase = options.useExisting.dir().uri } + const newIndexDoc = kb.sym(newBase + 'index.ttl') + const indexInstance = kb.sym(newBase + 'index.ttl#this') + const newDetailsDoc = kb.sym(newBase + 'details.ttl') + const newInstance = kb.sym(newDetailsDoc.uri + '#event') const newResultsDoc = kb.sym(newBase + 'results.ttl') const toBeCopied = options.noIndexHTML @@ -211,7 +202,7 @@ export const schedulePane = { const fun = function copyItem (item) { agenda.push(function () { const newURI = newBase + item.local - console.log('Copying ' + base + item.local + ' to ' + newURI) + console.log('Copying ' + newBase + item.local + ' to ' + newURI) const setThatACL = function () { setACL2(newURI, false, function (ok, message) { @@ -229,7 +220,7 @@ export const schedulePane = { kb.fetcher .webCopy( - base + item.local, + newBase + item.local, newBase + item.local, item.contentType ) @@ -241,11 +232,11 @@ export const schedulePane = { }) .catch(err => { console.log( - 'FAILED to copy ' + base + item.local + ' : ' + err.message + 'FAILED to copy ' + newBase + item.local + ' : ' + err.message ) complainIfBad( false, - 'FAILED to copy ' + base + item.local + ' : ' + err.message + 'FAILED to copy ' + newBase + item.local + ' : ' + err.message ) }) }) @@ -253,6 +244,25 @@ export const schedulePane = { fun(item) } + agenda.push(function createIndexFile () { + kb.add(indexInstance, ns.rdf('type'), ns.sched('SchedulableEvent'), newIndexDoc) + updater.put( + newIndexDoc, + kb.statementsMatching(undefined, undefined, undefined, newIndexDoc), + 'text/turtle', + function (uri, ok, message) { + if (ok) { + agenda.shift()() + } else { + complainIfBad( + ok, + 'FAILED to save index file at: ' + newIndexDoc + ' : ' + message + ) + } + } + ) + }) + agenda.push(function createDetailsFile () { kb.add( newInstance,