diff --git a/src/dataContentPane.js b/src/dataContentPane.js index 749ebc75..9a72f656 100644 --- a/src/dataContentPane.js +++ b/src/dataContentPane.js @@ -45,7 +45,10 @@ export const dataContentPane = { statementsAsTables: function statementsAsTables (sts, context, initialRoots) { const myDocument = context.dom // const outliner = context.getOutliner(myDocument) - const rep = myDocument.createElement('table') + // The outer container groups one block per "root" subject. Each block holds + // a subject label and a
of its predicates/values. + const rep = myDocument.createElement('section') + rep.classList.add('data-content') const sz = $rdf.Serializer(context.session.store) const res = sz.rootSubjects(sts) let roots = res.roots @@ -57,10 +60,12 @@ export const dataContentPane = { const doneBnodes = {} // For preventing looping const referencedBnodes = {} // Bnodes which need to be named alas - // The property tree for a single subject or anonymous node + // The property tree for a single subject or anonymous node. Returns a + //
with one
per predicate followed by one + //
per value. Replaces the previous //
with rowspan. function propertyTree (subject) { - // print('Proprty tree for '+subject) - const rep = myDocument.createElement('table') + const rep = myDocument.createElement('dl') + rep.classList.add('property-list') let lastPred = null const sts = subjects[sz.toStr(subject)] // relevant statements if (!sts) { @@ -69,17 +74,10 @@ export const dataContentPane = { return rep } sts.sort() - let same = 0 - let predicateTD // The cell which holds the predicate - for (let i = 0; i < sts.length; i++) { - const st = sts[i] - const tr = myDocument.createElement('tr') + for (const st of sts) { if (st.predicate.uri !== lastPred) { - if (lastPred && same > 1) { - predicateTD.setAttribute('rowspan', '' + same) - } - predicateTD = myDocument.createElement('td') - predicateTD.setAttribute('class', 'pred') + const dt = myDocument.createElement('dt') + dt.classList.add('pred') const anchor = myDocument.createElement('a') anchor.setAttribute('href', st.predicate.uri) anchor.addEventListener( @@ -92,18 +90,15 @@ export const dataContentPane = { UI.utils.predicateLabelForXML(st.predicate) ) ) - predicateTD.appendChild(anchor) - tr.appendChild(predicateTD) + dt.appendChild(anchor) + rep.appendChild(dt) lastPred = st.predicate.uri - same = 0 } - same++ - const objectTD = myDocument.createElement('td') - objectTD.appendChild(objectTree(st.object)) - tr.appendChild(objectTD) - rep.appendChild(tr) + const dd = myDocument.createElement('dd') + dd.classList.add('obj') + dd.appendChild(objectTree(st.object)) + rep.appendChild(dd) } - if (lastPred && same > 1) predicateTD.setAttribute('rowspan', '' + same) return rep } @@ -150,26 +145,19 @@ export const dataContentPane = { return anchor } doneBnodes[obj.toNT()] = true // Flag to prevent infinite recursion in propertyTree - const newTable = propertyTree(obj) - doneBnodes[obj.toNT()] = newTable // Track where we mentioned it first - if ( - UI.utils.ancestor(newTable, 'TABLE') && - UI.utils.ancestor(newTable, 'TABLE').style.backgroundColor === - 'white' - ) { - newTable.style.backgroundColor = '#eee' - } else { - newTable.style.backgroundColor = 'white' - } - return newTable + const newTree = propertyTree(obj) + newTree.classList.add('nestedBnode') + doneBnodes[obj.toNT()] = newTree // Track where we mentioned it first + return newTree } case 'Collection': - res = myDocument.createElement('table') - res.setAttribute('class', 'collectionAsTables') - for (let i = 0; i < obj.elements.length; i++) { - const tr = myDocument.createElement('tr') - res.appendChild(tr) - tr.appendChild(objectTree(obj.elements[i])) + // rdf:List → semantic ordered list with browser-provided numbering. + res = myDocument.createElement('ol') + res.classList.add('rdf-collection') + for (const elt of obj.elements) { + const li = myDocument.createElement('li') + li.appendChild(objectTree(elt)) + res.appendChild(li) } return res case 'Graph': @@ -199,20 +187,22 @@ export const dataContentPane = { ) } for (let i = 0; i < roots.length; i++) { - const tr = myDocument.createElement('tr') - tr.setAttribute('style', `background-color: ${i % 2 === 0 ? '#f0f0f0' : 'white'};`) - rep.appendChild(tr) - const subjectTD = myDocument.createElement('td') - tr.appendChild(subjectTD) - const TDTree = myDocument.createElement('td') - tr.appendChild(TDTree) + const subjBlock = myDocument.createElement('section') + subjBlock.classList.add('data-content__subject') + // Alternating background as visual separator (was previously row striping). + if (i % 2 === 0) subjBlock.classList.add('data-content__subject--even') + rep.appendChild(subjBlock) + + const subjLabel = myDocument.createElement('div') + subjLabel.classList.add('data-content__subject-label') const root = roots[i] if (root.termType === 'BlankNode') { - subjectTD.appendChild(myDocument.createTextNode(UI.utils.label(root))) // Don't recurse! + subjLabel.appendChild(myDocument.createTextNode(UI.utils.label(root))) // Don't recurse! } else { - subjectTD.appendChild(objectTree(root)) // won't have tree + subjLabel.appendChild(objectTree(root)) // won't have tree } - TDTree.appendChild(propertyTree(root)) + subjBlock.appendChild(subjLabel) + subjBlock.appendChild(propertyTree(root)) } for (const bNT in referencedBnodes) { // Add number to refer to diff --git a/src/outline/manager.css b/src/outline/manager.css index c1da1ff7..375240c0 100644 --- a/src/outline/manager.css +++ b/src/outline/manager.css @@ -1,14 +1,73 @@ /* Styles extracted from manager.js */ .obj { - margin: 0.2em; border: none; - padding: 0; vertical-align: top; } -.pred, .pred.internal { - /* Add any specific styles for predicate TDs here */ +/* Real data tables (dataContentPane's internal property tree). + These are legitimate s, so just give them consistent inter-cell spacing. */ +.tableFullWidth, +.collectionAsTables { + border-collapse: separate; + border-spacing: var(--spacing-sm, 0.5rem); +} + +/* rdf:List rendering: ordered list using the browser's native numbering. */ +.rdf-collection { + margin: 0; + padding-left: 2em; /* room for the native marker */ +} +.rdf-collection > li { + padding: 0.1em 0; +} +.rdf-collection > li::marker { + color: var(--color-text-muted, #6B7280); +} + +/* Description list of a subject's predicates and values, emitted by appendPropertyTRs. + The two-column visual layout is purely presentational — semantically it stays a
. */ +.property-list { + display: grid; + grid-template-columns: minmax(8rem, max-content) 1fr; + gap: 0.35em var(--spacing-sm, 0.5rem); + margin: 0; + align-items: start; +} +.property-list > dt { + grid-column: 1; + font-weight: 500; + color: var(--color-text-muted, #6B7280); +} +.property-list > dd { + grid-column: 2; + margin: 0; + min-width: 0; /* allow long URIs to wrap inside the cell */ +} +.property-list > dd.property-more { + grid-column: 2; +} +.property-list > dd.property-more > details { + display: contents; +} +.property-list > dd.property-more > details > summary { + cursor: pointer; + color: var(--color-text-muted, #6B7280); + font-size: 0.9em; +} + +/* dataContentPane: per-subject blocks (was a striped
) */ +.data-content { + display: block; +} +.data-content__subject { + padding: 0.5em 0.75em; +} +.data-content__subject--even { + background-color: var(--color-row-alt, #f0f0f0); +} +.data-content .property-list { + margin: 0; } .iconTD { @@ -21,6 +80,7 @@ .labelTD { width: 100%; + padding: 0; } .paneIconTray { @@ -63,6 +123,14 @@ background: var(--color-background, #F8F9FB) !important; } +/* Native disclosure for the object cell of a predicate row */ +.obj-disclosure { display: inline-block; } +.obj-disclosure > summary { cursor: pointer; } +.obj-disclosure > .obj-expanded { + margin-top: 0.25em; + padding-left: 1em; +} + .placeholderTable { width: 100%; } diff --git a/src/outline/manager.js b/src/outline/manager.js index b57ef366..d1f5b35e 100644 --- a/src/outline/manager.js +++ b/src/outline/manager.js @@ -193,23 +193,29 @@ export default function (context) { obj, view, deleteNode, - statement + statement, + elementName, + source ) { - const td = dom.createElement('td') + const td = dom.createElement(elementName || 'td') td.classList.add('obj') + if (source) td.dataset.outlineSource = source td.setAttribute('notSelectable', 'false') - td.style.margin = '0.2em' if (!obj) { td.textContent = 'No object available.' return td } - td.style.border = 'none' - td.style.padding = '0' - td.style.verticalAlign = 'top' - const theClass = 'obj' - // set about and put 'expand' icon - if ( + if (kb.whether(obj, UI.ns.rdf('type'), UI.ns.link('Request'))) { + td.classList.add('undetermined') + } // @@? why-timbl + + if (!view) { + // view should be a function pointer + view = viewAsBoringDefault + } + + const isExpandable = obj.termType === 'NamedNode' || obj.termType === 'BlankNode' || (obj.termType === 'Literal' && @@ -217,27 +223,26 @@ export default function (context) { (obj.value.slice(0, 6) === 'ftp://' || obj.value.slice(0, 8) === 'https://' || obj.value.slice(0, 7) === 'http://')) - ) { - td.setAttribute('about', obj.toNT()) - td.appendChild( - UI.utils.AJARImage( - UI.icons.originalIconBase + 'tbl-expand-trans.png', - 'expand', - undefined, - dom - ) - ).addEventListener('click', expandMouseDownListener) - } - td.setAttribute('class', theClass) // this is how you find an object - if (kb.whether(obj, UI.ns.rdf('type'), UI.ns.link('Request'))) { - td.className = 'undetermined' - } // @@? why-timbl - if (!view) { - // view should be a function pointer - view = viewAsBoringDefault + if (isExpandable) { + // Use native
/ for disclosure of the inlined sub-subject + td.setAttribute('about', obj.toNT()) + const details = dom.createElement('details') + details.classList.add('obj-disclosure') + const summary = details.appendChild(dom.createElement('summary')) + summary.appendChild(view(obj)) + const expanded = details.appendChild(dom.createElement('div')) + expanded.classList.add('obj-expanded') + details.addEventListener('toggle', () => { + if (details.open && !expanded.firstChild) { + // Lazy: fetch+render the sub-subject's property table on first open. + outlineExpand(expanded, obj, {}) + } + }) + td.appendChild(details) + } else { + td.appendChild(view(obj)) } - td.appendChild(view(obj)) if (deleteNode) { appendRemoveIcon(td, obj, deleteNode) } @@ -258,9 +263,10 @@ export default function (context) { predicate, newTr, inverse, - internal + internal, + elementName ) { - const predicateTD = dom.createElement('TD') + const predicateTD = dom.createElement(elementName || 'TD') predicateTD.setAttribute('about', predicate.toNT()) predicateTD.setAttribute('class', internal ? 'pred internal' : 'pred') @@ -278,7 +284,7 @@ export default function (context) { lab = lab ? lab.slice(0, 1).toUpperCase() + lab.slice(1) : '...' // if (kb.statementsMatching(predicate,rdf('type'), UI.ns.link('Request')).length) predicateTD.className='undetermined'; - const labelTD = dom.createElement('TD') + const labelTD = dom.createElement('span') labelTD.classList.add('labelTD') labelTD.setAttribute('notSelectable', 'true') labelTD.appendChild(dom.createTextNode(lab)) @@ -588,7 +594,7 @@ export default function (context) { } if (containerHost) { - const OutlineView = document.createElement('table') + const OutlineView = document.createElement('div') OutlineView.id = 'OutlineView' OutlineView.classList.add('outline-view') OutlineView.setAttribute('aria-label', 'Resource browser') @@ -654,12 +660,12 @@ export default function (context) { async function expandedHeaderTR (subject, requiredPane, options) { async function renderPaneIconTray (td, options = {}) { const paneShownStyle = - 'width: 24px; border-radius: 0.5em; border-top: solid #222 1px; border-left: solid #222 0.1em; border-bottom: solid #eee 0.1em; border-right: solid #eee 0.1em; margin-left: 1em; padding: 3px; background-color: #ffd;' + 'width: 24px; border-radius: 0.5em; border-top: solid #222 1px; border-left: solid #222 0.1em; border-bottom: solid #eee 0.1em; border-right: solid #eee 0.1em; padding: 3px; background-color: #ffd;' const paneHiddenStyle = - 'width: 24px; border-radius: 0.5em; margin-left: 1em; padding: 3px' + 'width: 24px; border-radius: 0.5em; padding: 3px' const paneIconTray = td.appendChild(dom.createElement('nav')) paneIconTray.style = - 'display:flex; justify-content: flex-start; align-items: center;' + 'display:flex; justify-content: flex-start; align-items: center; gap: 1em; flex-wrap: wrap;' const relevantPanes = options.hideList ? [] @@ -700,14 +706,20 @@ export default function (context) { ico.addEventListener( 'click', function (event) { - let containingTable - // Find the containing table for this subject - for (containingTable = td; containingTable.parentNode; containingTable = containingTable.parentNode) { - if (containingTable.nodeName === 'TABLE') break - } - if (containingTable.nodeName !== 'TABLE') { + // Find the per-subject scaffold that holds [header, panes...]. + // propertyTable() now builds this as
, so + // we look for it first; fall back to the outer view / a real
+ // for any legacy callers. + let containingTable = td.closest('.tableFullWidth, #OutlineView, .outline-view, table') + if (!containingTable) { throw new Error('outline: internal error.') } + // Find the subject's header row (direct child of containingTable) so + // panes get inserted immediately below their subject, not at the end. + let subjectRow = td + while (subjectRow && subjectRow.parentNode !== containingTable) { + subjectRow = subjectRow.parentNode + } const removePanes = function (specific) { for (let d = containingTable.firstChild; d; d = d.nextSibling) { if (typeof d.pane !== 'undefined') { @@ -740,7 +752,9 @@ export default function (context) { try { paneDiv = pane.render(subject, context, options) } catch (e) { - // Easier debugging for pane developers + // Easier debugging for pane developers — log the Error + // object so DevTools applies source maps to the stack. + console.error(e) paneDiv = dom.createElement('div') paneDiv.setAttribute('class', 'exceptionPane') const pre = dom.createElement('pre') @@ -756,15 +770,18 @@ export default function (context) { ) { dom.getElementById('queryButton').removeAttribute('style') } - const second = containingTable.firstChild.nextSibling - const row = dom.createElement('tr') - const cell = row.appendChild(dom.createElement('td')) - cell.setAttribute('colspan', '2') - cell.style.textAlign = 'left' - cell.style.width = '100%' - cell.appendChild(paneDiv) - if (second) containingTable.insertBefore(row, second) - else containingTable.appendChild(row) + const row = dom.createElement('div') + row.style.textAlign = 'left' + row.style.width = '100%' + row.appendChild(paneDiv) + // Insert directly after the subject's header row so panes stay grouped + // with their subject (was: insertBefore second-child of the whole view). + const insertAfter = subjectRow || containingTable.firstChild + if (insertAfter && insertAfter.nextSibling) { + containingTable.insertBefore(row, insertAfter.nextSibling) + } else { + containingTable.appendChild(row) + } row.pane = pane row.paneButton = ico } @@ -806,22 +823,23 @@ export default function (context) { return paneIconTray } // renderPaneIconTray - // Body of expandedHeaderTR - const tr = dom.createElement('tr') + // Body of expandedHeaderTR. + // Despite the legacy name, this now returns a
(the outer scaffold is + // no longer a
) holding the pane-icon tray. + const tr = dom.createElement('div') if (options.hover) { // By default no hide till hover as community deems it confusing tr.setAttribute('class', 'hoverControl') } - const td = tr.appendChild(dom.createElement('td')) + const td = tr.appendChild(dom.createElement('div')) td.setAttribute( 'style', - 'margin: 0.2em; border: none; padding-top: 0; padding-bottom: 0; vertical-align: top;' + + 'margin: 0.2em;' + 'display:flex; justify-content: space-between; flex-direction: row;' + 'background-color: var(--color-background, #F8F9FB);' ) td.setAttribute('notSelectable', 'true') td.setAttribute('about', subject.toNT()) - td.setAttribute('colspan', '2') // Stuff at the right about the subject const header = td.appendChild(dom.createElement('div')) @@ -911,8 +929,13 @@ export default function (context) { // if (!pane) pane = panes.defaultPane; if (!table) { - // Create a new property table - table = dom.createElement('table') + // Create a new property scaffold. + // The outer element is a
, not a
: this scaffold only ever + // holds a header (pane-icon tray) and a single full-width pane content + // row, so it does not need
semantics. Real tabular data + // (predicate/object rows) is rendered by individual panes in their own + // inner
. + table = dom.createElement('div') table.classList.add('tableFullWidth') expandedHeaderTR(subject, pane, options).then(tr1 => { table.appendChild(tr1) @@ -923,7 +946,9 @@ export default function (context) { UI.log.info('outline: Rendering pane (1): ' + tr1.firstPane.name) paneDiv = tr1.firstPane.render(subject, context, options) } catch (e) { - // Easier debugging for pane developers + // Easier debugging for pane developers — log the Error object so + // DevTools applies source maps to the stack. + console.error(e) paneDiv = dom.createElement('div') paneDiv.setAttribute('class', 'exceptionPane') const pre = dom.createElement('pre') @@ -931,13 +956,11 @@ export default function (context) { pre.appendChild(dom.createTextNode(UI.utils.stackString(e))) } - const row = dom.createElement('tr') - const cell = row.appendChild(dom.createElement('td')) - cell.setAttribute('colspan', '2') - cell.style.textAlign = 'left' - cell.style.width = '100%' - cell.style.backgroundColor = 'var(--color-background, #F8F9FB)' - cell.appendChild(paneDiv) + const row = dom.createElement('div') + row.style.textAlign = 'left' + row.style.width = '100%' + row.style.backgroundColor = 'var(--color-background, #F8F9FB)' + row.appendChild(paneDiv) if ( tr1.firstPane.requireQueryButton && dom.getElementById('queryButton') @@ -972,14 +995,19 @@ export default function (context) { this.propertyTR = propertyTR // / ////////// Property list + // + // Renders the subject's outgoing triples as a
+ // containing one
per predicate and one
per object value, then + // appends that
to `parent`. Earlier this function appended orphan + //
s directly to `parent` (a
), which produced invalid markup. function appendPropertyTRs (parent, plist, inverse, predicateFilter) { - // UI.log.info('@appendPropertyTRs, 'this' is %s, dom is %s, '+ // Gives 'can't access dead object' - // 'thisOutline.document is %s', this, dom.location, thisOutline.document.location); - // UI.log.info('@appendPropertyTRs, dom is now ' + this.document.location); - // UI.log.info('@appendPropertyTRs, dom is now ' + thisOutline.document.location); UI.log.debug('Property list length = ' + plist.length) if (plist.length === 0) return '' - let sel, j, k + const dl = dom.createElement('dl') + dl.classList.add('property-list') + if (inverse) dl.classList.add('property-list--inverse') + parent.appendChild(dl) + let sel if (inverse) { sel = function (x) { return x.subject @@ -992,204 +1020,83 @@ export default function (context) { plist = plist.sort(UI.utils.RDFComparePredicateObject) } - const max = plist.length - for (j = 0; j < max; j++) { - // squishing together equivalent properties I think - let s = plist[j] - // if (s.object == parentSubject) continue; // that we knew - - // Avoid predicates from other panes - if (predicateFilter && !predicateFilter(s.predicate, inverse)) continue - - const tr = propertyTR(dom, s, inverse) - parent.appendChild(tr) - const predicateTD = tr.firstChild // we need to kludge the rowspan later - - let defaultpropview = views.defaults[s.predicate.uri] - - // LANGUAGE PREFERENCES WAS AVAILABLE WITH FF EXTENSION - get from elsewhere? + const langPref = outline.labeller.LanguagePreference + const MAX_BEFORE_OVERFLOW = 10 - let dups = 0 // How many rows have the same predicate, -1? - let langTagged = 0 // how many objects have language tags? - let myLang = 0 // Is there one I like? + const max = plist.length + let j = 0 + while (j < max) { + const s = plist[j] + if (predicateFilter && !predicateFilter(s.predicate, inverse)) { + j++ + continue + } - for ( - k = 0; - k + j < max && plist[j + k].predicate.sameTerm(s.predicate); - k++ - ) { - if (k > 0 && sel(plist[j + k]).sameTerm(sel(plist[j + k - 1]))) dups++ - if (sel(plist[j + k]).lang && outline.labeller.LanguagePreference) { - langTagged += 1 - if ( - sel(plist[j + k]).lang.indexOf( - outline.labeller.LanguagePreference - ) >= 0 - ) { - myLang++ - } + // Find the run of consecutive statements with the same predicate. + let runEnd = j + while (runEnd < max && plist[runEnd].predicate.sameTerm(s.predicate)) runEnd++ + const run = plist.slice(j, runEnd) + + // Emit the
for this predicate. + const dt = thisOutline.outlinePredicateTD(s.predicate, null, inverse, false, 'dt') + dt.AJAR_statement = s + dt.AJAR_inverse = inverse + dl.appendChild(dt) + + // Decide which values to show. Language preference: when every value is + // lang-tagged and at least one matches, show only the matching one(s). + let langTagged = 0 + const matching = [] + for (const st of run) { + if (sel(st).lang && langPref) { + langTagged++ + if (sel(st).lang.indexOf(langPref) >= 0) matching.push(st) } } + let valuesToRender + let firstTag = 'predicate-row' + if (langPref && langTagged === run.length && matching.length > 0) { + valuesToRender = matching + firstTag = 'lang-preferred' + } else { + // De-duplicate exact same-term object values + const seen = new Set() + valuesToRender = run.filter(st => { + const key = sel(st).toNT() + if (seen.has(key)) return false + seen.add(key) + return true + }) + } - /* Display only the one in the preferred language - ONLY in the case (currently) when all the values are tagged. - Then we treat them as alternatives. */ + const defaultpropview = views.defaults[s.predicate.uri] + const overflow = [] - if (myLang > 0 && langTagged === dups + 1) { - for (let k = j; k <= j + dups; k++) { - if ( - outline.labeller.LanguagePreference && - sel(plist[k]).lang.indexOf(outline.labeller.LanguagePreference) >= 0 - ) { - tr.appendChild( - thisOutline.outlineObjectTD( - sel(plist[k]), - defaultpropview, - undefined, - s - ) - ) - break - } + valuesToRender.forEach((st, idx) => { + const tag = idx === 0 ? firstTag : 'duplicate-pred-row' + const dd = thisOutline.outlineObjectTD( + sel(st), defaultpropview, undefined, st, 'dd', tag + ) + if (idx < MAX_BEFORE_OVERFLOW) { + dl.appendChild(dd) + } else { + overflow.push(dd) } - j += dups // extra push - continue - } - - tr.appendChild( - thisOutline.outlineObjectTD(sel(s), defaultpropview, undefined, s) - ) + }) - /* Note: showNobj shows between n to 2n objects. - * This is to prevent the case where you have a long list of objects - * shown, and dangling at the end is '1 more' (which is easily ignored) - * Therefore more objects are shown than hidden. - */ - - tr.showNobj = function (n) { - const predDups = k - dups - const show = 2 * n < predDups ? n : predDups - const showLaterArray = [] - if (predDups !== 1) { - predicateTD.setAttribute( - 'rowspan', - show === predDups ? predDups : n + 1 - ) - let l - if (show < predDups && show === 1) { - // what case is this... - predicateTD.setAttribute('rowspan', 2) - } - let displayed = 0 // The number of cells generated-1, - // all duplicate thing removed - for (l = 1; l < k; l++) { - // This detects the same things - if ( - !kb - .canon(sel(plist[j + l])) - .sameTerm(kb.canon(sel(plist[j + l - 1]))) - ) { - displayed++ - s = plist[j + l] - defaultpropview = views.defaults[s.predicate.uri] - const trObj = dom.createElement('tr') - trObj.style.colspan = '1' - trObj.appendChild( - thisOutline.outlineObjectTD( - sel(plist[j + l]), - defaultpropview, - undefined, - s - ) - ) - trObj.AJAR_statement = s - trObj.AJAR_inverse = inverse - parent.appendChild(trObj) - if (displayed >= show) { - trObj.style.display = 'none' - showLaterArray.push(trObj) - } - } else { - // ToDo: show all the data sources of this statement - UI.log.info('there are duplicates here: %s', plist[j + l - 1]) - } - } - // @@a quick fix on the messing problem. - if (show === predDups) { - predicateTD.setAttribute('rowspan', displayed + 1) - } - } // end of if (predDups!==1) - - if (show < predDups) { - // Add the x more
here - const moreTR = dom.createElement('tr') - const moreTD = moreTR.appendChild(dom.createElement('td')) - moreTD.setAttribute( - 'style', - 'margin: 0.2em; border: none; padding: 0; vertical-align: top;' - ) - moreTD.setAttribute('notSelectable', 'false') - if (predDups > n) { - // what is this for?? - const small = dom.createElement('a') - moreTD.appendChild(small) - - const predToggle = (function (f) { - return f(predicateTD, k, dups, n) - })(function (predicateTD, k, dups, n) { - return function (display) { - small.innerHTML = '' - if (display === 'none') { - small.appendChild( - UI.utils.AJARImage( - UI.icons.originalIconBase + 'tbl-more-trans.png', - 'more', - 'See all', - dom - ) - ) - small.appendChild( - dom.createTextNode(predDups - n + ' more...') - ) - predicateTD.setAttribute('rowspan', n + 1) - } else { - small.appendChild( - UI.utils.AJARImage( - UI.icons.originalIconBase + 'tbl-shrink.png', - '(less)', - undefined, - dom - ) - ) - predicateTD.setAttribute('rowspan', predDups + 1) - } - for (let i = 0; i < showLaterArray.length; i++) { - const trObj = showLaterArray[i] - trObj.style.display = display - } - } - }) // ??? - let current = 'none' - const toggleObj = function (event) { - predToggle(current) - current = current === 'none' ? '' : 'none' - if (event) event.stopPropagation() - return false // what is this for? - } - toggleObj() - small.addEventListener('click', toggleObj, false) - } // if(predDups>n) - parent.appendChild(moreTR) - } // if - } // tr.showNobj - - tr.showAllobj = function () { - tr.showNobj(k - dups) + // If there's overflow, wrap the extras in a
/ "+ N more" + // disclosure so the user can opt to see them all. + if (overflow.length > 0) { + const moreDd = dom.createElement('dd') + moreDd.classList.add('property-more') + const details = moreDd.appendChild(dom.createElement('details')) + const summary = details.appendChild(dom.createElement('summary')) + summary.textContent = '+ ' + overflow.length + ' more' + for (const dd of overflow) details.appendChild(dd) + dl.appendChild(moreDd) } - tr.showNobj(10) - - j += k - 1 // extra push + j = runEnd } } // appendPropertyTRs @@ -1202,7 +1109,7 @@ export default function (context) { global.termWidget = termWidget termWidget.construct = function (dom) { dom = dom || document - const td = dom.createElement('TD') + const td = dom.createElement('span') td.setAttribute( 'style', 'margin: 0.2em; border: none; padding: 0; vertical-align: top;' @@ -2262,7 +2169,7 @@ export default function (context) { deleteNode = level.parentNode } thisOutline.replaceTD( - thisOutline.outlineObjectTD(subject, myview, deleteNode, statement), + thisOutline.outlineObjectTD(subject, myview, deleteNode, statement, undefined, 'collapse-replace'), level ) } // outlineCollapse @@ -2342,11 +2249,11 @@ export default function (context) { } function GotoSubjectDefault () { - const tr = dom.createElement('TR') - tr.style.verticalAlign = 'top' - table.appendChild(tr) - const td = thisOutline.outlineObjectTD(subject, undefined, tr) - tr.appendChild(td) + const row = dom.createElement('span') + row.classList.add('subject-row') + table.appendChild(row) + const td = thisOutline.outlineObjectTD(subject, undefined, row, undefined, 'div', 'subject-cell') + row.appendChild(td) return td } @@ -2456,31 +2363,34 @@ export default function (context) { rep.appendChild(dom.createTextNode(UI.utils.label(obj))) } } else if (obj.termType === 'Collection') { - // obj.elements is an array of the elements in the collection - rep = dom.createElement('table') - rep.classList.add('tableFullWidth') + // An rdf:List is a one-dimensional ordered sequence.
    /
  1. is the + // correct element: the browser provides automatic numbering, and + // assistive tech announces it as "list, N items" with positional cues. + rep = dom.createElement('ol') + rep.classList.add('rdf-collection') rep.setAttribute('about', obj.toNT()) - /* Not sure which looks best -- with or without. I think without - - var tr = rep.appendChild(document.createElement('tr')); - tr.appendChild(document.createTextNode( - obj.elements.length ? '(' + obj.elements.length+')' : '(none)')); - */ - for (let i = 0; i < obj.elements.length; i++) { - const elt = obj.elements[i] - const row = rep.appendChild(dom.createElement('tr')) - const numcell = row.appendChild(dom.createElement('td')) - numcell.classList.add('obj') - numcell.setAttribute('notSelectable', 'false') - numcell.setAttribute('about', obj.toNT()) - numcell.innerHTML = i + 1 + ')' - row.appendChild(thisOutline.outlineObjectTD(elt)) + for (const elt of obj.elements) { + const li = rep.appendChild(dom.createElement('li')) + li.setAttribute('about', obj.toNT()) + li.appendChild( + thisOutline.outlineObjectTD( + elt, undefined, undefined, undefined, 'div', 'collection-element' + ) + ) } } else if (obj.termType === 'Graph') { - rep = paneRegistry - .byName('dataContentPane') - .statementsAsTables(obj.statements, context) - rep.setAttribute('class', 'nestedFormula') + // The pane is registered as 'dataContents' (not 'dataContentPane'). + // Fall back to a plain text label if the lookup somehow fails so we + // don't crash the surrounding render. + const dataContents = paneRegistry.byName('dataContents') + if (dataContents && typeof dataContents.statementsAsTables === 'function') { + rep = dataContents.statementsAsTables(obj.statements, context) + rep.setAttribute('class', 'nestedFormula') + } else { + UI.log.warn('viewAsBoringDefault: dataContents pane not available for Graph value') + rep = dom.createElement('span') + rep.textContent = '[graph: ' + obj.statements.length + ' statement(s)]' + } } else { UI.log.error('Object ' + obj + ' has unknown term type: ' + obj.termType) rep = dom.createTextNode('[unknownTermType:' + obj.termType + ']') diff --git a/src/outline/userInput.js b/src/outline/userInput.js index e6a7af47..fa670ec2 100644 --- a/src/outline/userInput.js +++ b/src/outline/userInput.js @@ -900,6 +900,7 @@ export function UserInput (outline) { } }) } catch (e) { + console.error(e) UI.log.error( 'Exception trying to insert statement ' + insertTr.AJAR_statement + @@ -2327,6 +2328,7 @@ export function UserInput (outline) { }) } catch (e) { // outline.UserInput.deleteTriple(newTd,true); + console.error(e) UI.log.error( 'userinput.js (object): exception trying to insert statement ' + s +