/ 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. /- 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 +
|