diff --git a/index.js b/index.js index d802e61..a1a9c11 100644 --- a/index.js +++ b/index.js @@ -118,8 +118,6 @@ exports.init = function (sbot, config) { var serveItems = createItemsHandler({ sbot: sbot, defaultOpts: defaultOpts, - getMsg: getMsg, - getAbout: getAbout, addAuthorAbout: addAuthorAbout, renderItemGrid: renderItemGrid, wrapPage: wrapPage, diff --git a/items.js b/items.js index 0344eea..c0c00fb 100644 --- a/items.js +++ b/items.js @@ -9,6 +9,12 @@ var getFollows = require('./lib/follows') // kiosk feed ids: adding a kiosk means following it from the pub, and its items // appear here on the next cache miss. No code change, no edit to cust.ooo. // +// ITEMS ONLY. A `nft: "give"` message is a change of custody, not a thing - it +// has no photo of its own and describes an item already on the page, so showing +// it here listed the same object twice. Custody is answered one level down: the +// card links to the item's thread, where the mint and every hand-off since are +// in order. +// // Two facts about the data that this file depends on, both verified against the // live log and both easy to get wrong: // * `custodisco` is the STRING "true", not a boolean. @@ -45,14 +51,17 @@ function itemCaption (c) { .trim() } -function isCustodisco (msg) { +// Every custodisco message carries nft: "mint" or "give" - checked across all +// 407 of them, none lacks the field - so matching "mint" is exact rather than +// merely excluding gives. +function isItem (msg) { var c = msg && msg.value && msg.value.content - return !!c && typeof c === 'object' && c.custodisco === 'true' + return !!c && typeof c === 'object' && + c.custodisco === 'true' && c.nft === 'mint' } // ctx supplies the pieces that live in index.js's init closure: -// sbot, defaultOpts, getMsg, getAbout, addAuthorAbout, respond, toPull, -// renderItemGrid, wrapPage +// sbot, defaultOpts, addAuthorAbout, respond, toPull, renderItemGrid, wrapPage module.exports = function createItemsHandler (ctx) { var cache = null // { at, msgs, feeds } @@ -73,7 +82,7 @@ module.exports = function createItemsHandler (ctx) { pull( ctx.sbot.createLogStream({ reverse: true }), pull.filter(function (msg) { - return isCustodisco(msg) && authors[msg.value.author] === true + return isItem(msg) && authors[msg.value.author] === true }), pull.collect(function (err, msgs) { if (err) return cb(err) @@ -81,10 +90,17 @@ module.exports = function createItemsHandler (ctx) { // whenever an old feed is backfilled. Sort so "newest first" and // the ?before cursor both mean the same thing. msgs.sort(function (a, b) { return b.value.timestamp - a.value.timestamp }) + // Count the feeds that actually contributed an item, not the size + // of the follow set: the pub follows itself and publishes no items, + // so the follow set overstates it by one and would keep drifting + // as feeds are followed for other reasons. + var contributing = Object.create(null) + msgs.forEach(function (m) { contributing[m.value.author] = true }) + cache = { at: Date.now(), msgs: msgs, - feeds: Object.keys(authors).length + feeds: Object.keys(contributing).length } cb(null, cache) }) @@ -93,40 +109,11 @@ module.exports = function createItemsHandler (ctx) { }) } - // A transfer carries no photo of its own, so borrow the parent item's and - // resolve the new steward's display name. Both lookups go through the LRU - // memos index.js already keeps. - function addTransferContext (msg, cb) { + function addItemContext (msg, cb) { var c = msg.value.content - if (c.nft !== 'give') { - msg.itemPhoto = itemPhoto(c) - msg.itemCaption = itemCaption(c) - return cb(null, msg) - } - - var pending = 2 - function done () { - if (--pending) return - cb(null, msg) - } - - if (c.root) { - ctx.getMsg(c.root, function (err, root) { - var rc = root && root.value && root.value.content - if (rc) { - msg.itemPhoto = itemPhoto(rc) - msg.itemCaption = itemCaption(rc) - } - done() - }) - } else done() - - if (c.target) { - ctx.getAbout(c.target, function (err, about) { - if (about) msg.stewardAbout = about - done() - }) - } else done() + msg.itemPhoto = itemPhoto(c) + msg.itemCaption = itemCaption(c) + cb(null, msg) } return function serveItems (req, res, query) { @@ -151,7 +138,7 @@ module.exports = function createItemsHandler (ctx) { pull( pull.values(page), paramap(ctx.addAuthorAbout, 8), - paramap(addTransferContext, 8), + paramap(addItemContext, 8), pull( ctx.renderItemGrid(ctx.defaultOpts, { total: data.msgs.length, @@ -172,4 +159,4 @@ module.exports = function createItemsHandler (ctx) { module.exports.itemPhoto = itemPhoto module.exports.itemCaption = itemCaption -module.exports.isCustodisco = isCustodisco +module.exports.isItem = isItem diff --git a/render.js b/render.js index 1bf4a3e..d6e7ea1 100644 --- a/render.js +++ b/render.js @@ -392,18 +392,6 @@ var styles = ` text-align: center; } .item-photo-missing::after { content: "photo not fetched yet"; } - .item-card-give .item-photo { opacity: 0.55; } - .item-badge { - position: absolute; - left: 0; right: 0; bottom: 0; - padding: 6px 8px; - background-color: rgba(33, 37, 41, 0.82); - color: #f1f3f5; - font-size: 13px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } .item-meta { padding: 10px 12px; display: flex; flex-direction: column; gap: 3px; } .item-caption { line-height: 1.35em; @@ -721,8 +709,9 @@ function renderShowAll(showAll, url) { // The custo item grid (/items) // // Cards, not articles: this is a collection of things, and a single-column feed -// of 300 photos reads like a mailing list rather than an archive. A transfer -// borrows the photo of the item it transfers, so the grid stays regular. +// of 300 photos reads like a mailing list rather than an archive. Items only - +// who holds an item is answered by its thread, one click in, not by an overlay +// on the grid. // --------------------------------------------------------------------------- function renderItemGrid(opts, meta) { @@ -734,7 +723,7 @@ function renderItemGrid(opts, meta) { } function itemsHeader(opts, meta) { - var counted = meta.total === 1 ? '1 item' : meta.total + ' items and transfers' + var counted = meta.total === 1 ? '1 item' : meta.total + ' items' var feeds = meta.feeds === 1 ? '1 feed' : meta.feeds + ' feeds' return h('header.items-header', h('h1', 'custo items'), @@ -758,11 +747,6 @@ function itemsFooter(opts, meta) { function renderItemCard(opts, msg) { var c = msg.value.content - var isGive = c.nft === 'give' - - // A transfer links to the item it transfers, not to itself: the thread page - // shows the mint and every hand-off since, which is the interesting view. - var target = isGive && c.root ? c.root : msg.key var media if (msg.itemPhoto) { @@ -779,15 +763,12 @@ function renderItemCard(opts, msg) { media = h('div.item-photo.item-photo-missing') } - var badge = isGive - ? h('span.item-badge', '\u2192 ' + stewardName(msg, c)) - : '' + var caption = msg.itemCaption || itemFallbackCaption(c) - var caption = msg.itemCaption || (isGive ? 'an item' : itemFallbackCaption(c)) - - return h('a.item-card' + (isGive ? '.item-card-give' : ''), - { href: opts.base + encodeURIComponent(target) }, - h('div.item-media', media, badge), + // Links to the item's own thread: the mint, and every hand-off since. + return h('a.item-card', + { href: opts.base + encodeURIComponent(msg.key) }, + h('div.item-media', media), h('div.item-meta', h('span.item-caption', caption), h('span.item-by', msg.author && msg.author.name), @@ -795,11 +776,6 @@ function renderItemCard(opts, msg) { ).outerHTML } -function stewardName(msg, c) { - if (msg.stewardAbout && msg.stewardAbout.name) return msg.stewardAbout.name - return String(c.target || 'someone').substr(0, 10) + '\u2026' -} - function itemFallbackCaption(c) { return String(c.text || '').replace(/\s+/g, ' ').trim().substr(0, 140) || 'untitled' }