Module:HackerNewsUtil: Difference between revisions

From Rest of What I Know
No edit summary
No edit summary
 
(6 intermediate revisions by the same user not shown)
Line 1: Line 1:
local p = {}
local p = {}
local HN_SVG = '<svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="4 4 188 188" width="18"><path d="m4 4h188v188h-188z" fill="#f60"/><path d="m73.2521756 45.01 22.7478244 47.39130083 22.7478244-47.39130083h19.56569631l-34.32352071 64.48661468v41.49338532h-15.98v-41.49338532l-34.32352071-64.48661468z" fill="#fff"/></svg>'


local function trim(s)
local function trim(s)
Line 99: Line 97:
     :addClass('mw-hn-logo')
     :addClass('mw-hn-logo')
     :attr('aria-hidden', 'true')
     :attr('aria-hidden', 'true')
    :cssText('display:inline-block;width:18px;height:18px;background:#f60;color:#fff;text-align:center;line-height:18px;font-weight:bold;font-size:12px;font-family:Verdana,Geneva,sans-serif;position:relative;')
    :tag('span')
      :cssText('position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);')
      :wikitext('Y')


   header:tag('span'):wikitext('Hacker News')
   header:tag('span'):wikitext('Hacker News')
Line 104: Line 106:
   -- Title
   -- Title
   if title and title ~= '' then
   if title and title ~= '' then
     local displayTitle = isComment and ('Comment on ' .. title) or title
     local titleDiv = h:tag('div')
    h:tag('div')
       :addClass('mw-hn-title')
       :addClass('mw-hn-title')
       :cssText('font-weight:600;margin:2px 0 6px 0;')
       :cssText('font-weight:600;margin:2px 0 6px 0;')
       :wikitext(displayTitle)
    if isComment then
       titleDiv:tag('span')
        :cssText('font-weight:normal;font-style:italic;')
        :wikitext('Comment on')
      titleDiv:wikitext(' ' .. title)
    else
      titleDiv:wikitext(title)
    end
   end
   end


Line 125: Line 133:
   local function addBullet() meta:tag('span'):wikitext('•'):css('opacity','0.6') end
   local function addBullet() meta:tag('span'):wikitext('•'):css('opacity','0.6') end
   local hasContent = false
   local hasContent = false
  if author and author ~= '' then
    meta:wikitext(mw.text.nowiki(author))
    hasContent = true
  end


   if dateHuman and dateHuman ~= '' then
   if dateHuman and dateHuman ~= '' then
    if hasContent then addBullet() end
     meta:tag('time'):attr('datetime', dateISO or ''):wikitext(dateHuman)
     meta:tag('time'):attr('datetime', dateISO or ''):wikitext(dateHuman)
     hasContent = true
     hasContent = true

Latest revision as of 23:52, 20 October 2025

Documentation for this module may be created at Module:HackerNewsUtil/doc

local p = {}

local function trim(s)
  if type(s) ~= 'string' then return nil end
  return (s:gsub('^%s+', ''):gsub('%s+$', ''))
end

local function parse_from_url(url)
  if type(url) ~= 'string' then return {} end
  local host, id = url:match('^https?://([^/]+)/item%?id=(%d+)')
  if id then
    return { id = id, site = host }
  end
  return {}
end

-- Render plain text with paragraphs preserved.
-- Two or more newlines => new <p>. Single newline => <br>.
local function render_paragraph_text(parent, s)
  if type(s) ~= 'string' or s == '' then return end
  s = s:gsub('\r\n', '\n')
  local paras, i = {}, 1
  while true do
    local a, b = s:find('\n\n+', i)
    if not a then table.insert(paras, s:sub(i)); break end
    table.insert(paras, s:sub(i, a - 1))
    i = b + 1
  end
  for _, para in ipairs(paras) do
    local pnode = parent:tag('p')
    local first = true
    for line in (para .. '\n'):gmatch('(.-)\n') do
      if not first then pnode:tag('br') end
      pnode:wikitext(mw.text.nowiki(line))
      first = false
    end
  end
end

local function anchor_text(id, site)
  if id and site then
    return string.format('Permalink: HN • %s • %s', site, id)
  elseif id then
    return 'Permalink: HN • ' .. id
  else
    return 'Permalink'
  end
end

local function gen_refname(id, url)
  if id and id ~= '' then
    return 'hn-' .. id
  end
  return 'hn-' .. mw.hash.hashValue('md5', url or tostring(mw.title.getCurrentTitle()))
end

function p.render(frame)
  local args = frame:getParent() and frame:getParent().args or frame.args
  local url        = trim(args.url)
  local siteArg    = trim(args.site)
  local author     = trim(args.author)
  local authorURL  = trim(args.author_url)
  local title      = trim(args.title)
  local text       = trim(args.text)
  local dateHuman  = trim(args.date)
  local dateISO    = trim(args.datetime) or dateHuman
  local score      = trim(args.score)
  local comments   = trim(args.comments)
  local isComment  = trim(args.comment) == 'yes'
  local refname    = trim(args.refname)
  local noreference = (trim(args.noreference) == 'yes') or (trim(args.ref) == 'no')

  local parsed = parse_from_url(url or '')
  local id   = parsed.id
  local site = siteArg or parsed.site or 'news.ycombinator.com'

  -- Fallback permalink if url omitted but id present
  if (not url or url == '') and id then
    url = 'https://news.ycombinator.com/item?id=' .. id
  end

  -- Auto author URL if not provided and single author
  if author and author ~= '' and (not authorURL or authorURL == '') and not author:find('|') then
    local enc = mw.uri.encode(author, 'QUERY')
    authorURL = 'https://news.ycombinator.com/user?id=' .. enc
  end

  local h = mw.html.create('div')
    :addClass('mw-hn')
    :cssText('border:1px solid #e2e8f0;border-radius:8px;padding:12px 14px;margin:8px 0;background:#fff;')

  -- Header with icon via CSS data-URI
  local header = h:tag('div'):addClass('mw-hn-header')
    :cssText('font-size:0.9em;color:#475569;margin-bottom:6px;display:flex;gap:8px;align-items:center;')

  header:tag('span')
    :addClass('mw-hn-logo')
    :attr('aria-hidden', 'true')
    :cssText('display:inline-block;width:18px;height:18px;background:#f60;color:#fff;text-align:center;line-height:18px;font-weight:bold;font-size:12px;font-family:Verdana,Geneva,sans-serif;position:relative;')
    :tag('span')
      :cssText('position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);')
      :wikitext('Y')

  header:tag('span'):wikitext('Hacker News')

  -- Title
  if title and title ~= '' then
    local titleDiv = h:tag('div')
      :addClass('mw-hn-title')
      :cssText('font-weight:600;margin:2px 0 6px 0;')
    if isComment then
      titleDiv:tag('span')
        :cssText('font-weight:normal;font-style:italic;')
        :wikitext('Comment on')
      titleDiv:wikitext(' ' .. title)
    else
      titleDiv:wikitext(title)
    end
  end

  -- Body text (optional)
  if text and text ~= '' then
    local bq = h:tag('blockquote')
      :addClass('mw-hn-text')
      :cssText('margin:6px 0 10px 0;padding:0 0 0 12px;border-left:3px solid #e2e8f0;color:#0f172a;')
    render_paragraph_text(bq, text)
  end

  -- Meta line
  local meta = h:tag('div'):addClass('mw-hn-meta')
    :cssText('font-size:0.85em;color:#475569;display:flex;flex-wrap:wrap;gap:8px;align-items:center;')

  local function addBullet() meta:tag('span'):wikitext('•'):css('opacity','0.6') end
  local hasContent = false

  if author and author ~= '' then
    meta:wikitext(mw.text.nowiki(author))
    hasContent = true
  end

  if dateHuman and dateHuman ~= '' then
    if hasContent then addBullet() end
    meta:tag('time'):attr('datetime', dateISO or ''):wikitext(dateHuman)
    hasContent = true
  end

  if score and score ~= '' then
    if hasContent then addBullet() end
    meta:wikitext(tostring(score) .. ' points')
    hasContent = true
  end

  if comments and comments ~= '' then
    if hasContent then addBullet() end
    meta:wikitext(tostring(comments) .. ' comments')
    hasContent = true
  end

  -- Footnote reference (permalink)
  if url and url ~= '' and not noreference then
    local name = refname or gen_refname(id, url)

    -- Build reference text with author if provided
    local ref_parts = {}
    if author and author ~= '' then
      local parts = mw.text.split(author, '%s*|%s*')
      local author_links = {}
      for i, authorName in ipairs(parts) do
        local authorUrl = authorURL
        if not authorUrl or authorUrl == '' or #parts > 1 then
          local enc = mw.uri.encode(authorName, 'QUERY')
          authorUrl = 'https://news.ycombinator.com/user?id=' .. enc
        end
        table.insert(author_links, string.format('[%s %s]', authorUrl, mw.text.nowiki(authorName)))
      end
      table.insert(ref_parts, table.concat(author_links, ', '))
      table.insert(ref_parts, ' on ')
    end
    table.insert(ref_parts, string.format('[%s %s]', url, anchor_text(id, site)))

    local text_ref = table.concat(ref_parts, '')
    local ref_markup = frame:extensionTag('ref', text_ref, { name = name })
    meta:wikitext(' ' .. ref_markup)
  end

  return tostring(h)
end

return p