diff --git a/bbs.rb b/bbs.rb index 9c064da..849154e 100644 --- a/bbs.rb +++ b/bbs.rb @@ -65,41 +65,41 @@ MAIN_APP = BBS::Application.define do menubar do menu '&File' do - item '&Bulletin' do UI::Windows.bulletin(self, services: SERVICES) end - item '&System Info' do UI::Windows.system_info(self, services: SERVICES) end + item '&Bulletin' do UI::Windows::Bulletin.open(self, services: SERVICES) end + item '&System Info' do UI::Windows::SystemInfo.open(self, services: SERVICES) end separator item 'E&xit', shortcut: 'Alt-X' do :halt end end menu '&Messages' do - item '&Boards…', shortcut: 'F2' do UI::Windows.messages(self, services: SERVICES) end + item '&Boards…', shortcut: 'F2' do UI::Windows::Messages.open(self, services: SERVICES) end item '&New Post…', shortcut: 'F3' do board = SERVICES[:boards].boards.first - UI::Windows.compose_message(self, services: SERVICES, board: board) + UI::Windows::ComposeMessage.open(self, services: SERVICES, board: board) end end menu '&Files' do - item '&Blog Posts' do UI::Windows.wiki(self, services: SERVICES, category: 'blog', title: 'Blog Posts') end - item '&HowTo Guides' do UI::Windows.wiki(self, services: SERVICES, category: 'howto', title: 'HowTo Guides') end - item '&Game Catalog' do UI::Windows.games(self, services: SERVICES) end + item '&Blog Posts' do UI::Windows::Wiki.open(self, services: SERVICES, category: 'blog', title: 'Blog Posts') end + item '&HowTo Guides' do UI::Windows::Wiki.open(self, services: SERVICES, category: 'howto', title: 'HowTo Guides') end + item '&Game Catalog' do UI::Windows::Games.open(self, services: SERVICES) end end menu '&Users' do - item '&Online' do UI::Windows.online_users(self, services: SERVICES) end - item '&Last Callers' do UI::Windows.last_callers(self, services: SERVICES) end - item '&Profile…' do UI::Windows.profile(self, services: SERVICES) end + item '&Online' do UI::Windows::OnlineUsers.open(self, services: SERVICES) end + item '&Last Callers' do UI::Windows::LastCallers.open(self, services: SERVICES) end + item '&Profile…' do UI::Windows::Profile.open(self, services: SERVICES) end end menu '&Chat' do - item '&Open chat', shortcut: 'F4' do UI::Windows.chat(self, services: SERVICES) end + item '&Open chat', shortcut: 'F4' do UI::Windows::Chat.open(self, services: SERVICES) end end menu 'S&ysop' do item '&Console', shortcut: 'F10' do name = (context[:username] || '').downcase if SYSOPS.include?(name) - UI::Windows.sysop_console(self, services: SERVICES) + UI::Windows::SysopConsole.open(self, services: SERVICES) else BBS::Dialogs.message(self, 'Sysop access denied.', title: ' Sysop ') end @@ -123,12 +123,12 @@ MAIN_APP = BBS::Application.define do end status_hint('F1', 'Help') { BBS::Dialogs.message(self, 'Top menu: Alt+letter — Windows: Tab/Shift-Tab — Close: Esc') } - status_hint('F2', 'Boards') { UI::Windows.messages(self, services: SERVICES) } + status_hint('F2', 'Boards') { UI::Windows::Messages.open(self, services: SERVICES) } status_hint('F3', 'Post') { board = SERVICES[:boards].boards.first - UI::Windows.compose_message(self, services: SERVICES, board: board) + UI::Windows::ComposeMessage.open(self, services: SERVICES, board: board) } - status_hint('F4', 'Chat') { UI::Windows.chat(self, services: SERVICES) } + status_hint('F4', 'Chat') { UI::Windows::Chat.open(self, services: SERVICES) } status_hint('F10', 'Menu') { menubar.open(0) } status_hint('Alt-X', 'Exit') { :halt } diff --git a/lib/ui/windows/bulletin_window.rb b/lib/ui/windows/bulletin_window.rb index b0d0670..9cbb641 100644 --- a/lib/ui/windows/bulletin_window.rb +++ b/lib/ui/windows/bulletin_window.rb @@ -2,36 +2,36 @@ module UI module Windows - module_function - # Login splash: ASCII banner + last callers + new message counts. Closed # with Enter / Esc / clicking the OK button. - def bulletin(app, services:) - banner_lines = [ - " \e[1;33m _____ _ _ ____ ____ _____\e[0m", - " \e[1;33m|_ _|__ | | | |_ __ _ _ _ ___ | __ )| __ )/ ____|\e[0m", - " \e[1;33m | |/ _ \\ | | | __|/ _` | '_/ _ \\ | _ \\| _ \\\\___ \\\e[0m", - " \e[1;33m | | __/ | |___ | |_| (_| | || __/ | |_) | |_) |___) |\e[0m", - " \e[1;33m |_|\\___| |_____|\\__|\\__,_|_| \\___| |____/|____/|____/\e[0m", - ] - caller_lines = services[:last_callers].recent(3).map do |c| - " · #{c.username} \e[2;37m#{c.timestamp[0, 16].sub('T', ' ')}\e[0m" + class Bulletin + def self.open(app, services:) + banner_lines = [ + " \e[1;33m _____ _ _ ____ ____ _____\e[0m", + " \e[1;33m|_ _|__ | | | |_ __ _ _ _ ___ | __ )| __ )/ ____|\e[0m", + " \e[1;33m | |/ _ \\ | | | __|/ _` | '_/ _ \\ | _ \\| _ \\\\___ \\\e[0m", + " \e[1;33m | | __/ | |___ | |_| (_| | || __/ | |_) | |_) |___) |\e[0m", + " \e[1;33m |_|\\___| |_____|\\__|\\__,_|_| \\___| |____/|____/|____/\e[0m", + ] + caller_lines = services[:last_callers].recent(3).map do |c| + " · #{c.username} \e[2;37m#{c.timestamp[0, 16].sub('T', ' ')}\e[0m" + end + BBS::Windows::Info.open(app, + title: ' Welcome ', width: 70, height: 17, + body: [ + *banner_lines.map { |l| { text: l, style: :accent } }, + :spacer, + "Logged in as \e[1;33m#{app.context[:username] || 'Anonymous'}\e[0m", + :spacer, + "Online now: #{services[:online].count}", + "Total messages: #{services[:boards].total_count}", + :spacer, + 'Last callers:', + *caller_lines, + ], + buttons: [{ label: '&Continue', primary: true, cancel: true }] + ) end - BBS::Windows::Info.open(app, - title: ' Welcome ', width: 70, height: 17, - body: [ - *banner_lines.map { |l| { text: l, style: :accent } }, - :spacer, - "Logged in as \e[1;33m#{app.context[:username] || 'Anonymous'}\e[0m", - :spacer, - "Online now: #{services[:online].count}", - "Total messages: #{services[:boards].total_count}", - :spacer, - 'Last callers:', - *caller_lines, - ], - buttons: [{ label: '&Continue', primary: true, cancel: true }] - ) end end end diff --git a/lib/ui/windows/chat_window.rb b/lib/ui/windows/chat_window.rb index 6b6a586..7a81355 100644 --- a/lib/ui/windows/chat_window.rb +++ b/lib/ui/windows/chat_window.rb @@ -2,21 +2,22 @@ module UI module Windows - module_function + class Chat + def self.open(app, services:) + username = app.context[:username] || 'Anonymous' + BBS::Windows::Stream.open(app, + title: ' Live Chat ', + initial_lines: services[:chat].history.map { |l| format_chat_line(l) }, + placeholder: 'Type a message and press Enter…', + on_submit: ->(text, _a) { services[:chat].broadcast(username, text.strip) }, + on_poll: ->(a) { services[:chat].drain(a.session_id).map { |l| format_chat_line(l) } } + ) + end - def chat(app, services:) - username = app.context[:username] || 'Anonymous' - BBS::Windows::Stream.open(app, - title: ' Live Chat ', - initial_lines: services[:chat].history.map { |l| format_chat_line(l) }, - placeholder: 'Type a message and press Enter…', - on_submit: ->(text, _a) { services[:chat].broadcast(username, text.strip) }, - on_poll: ->(a) { services[:chat].drain(a.session_id).map { |l| format_chat_line(l) } } - ) - end - - def format_chat_line(line) - " \e[2;37m#{line.timestamp}\e[0m \e[1;33m#{line.username}\e[0m: #{line.text}" + def self.format_chat_line(line) + " \e[2;37m#{line.timestamp}\e[0m \e[1;33m#{line.username}\e[0m: #{line.text}" + end + private_class_method :format_chat_line end end end diff --git a/lib/ui/windows/games_window.rb b/lib/ui/windows/games_window.rb index 0ef740a..d8105c1 100644 --- a/lib/ui/windows/games_window.rb +++ b/lib/ui/windows/games_window.rb @@ -2,33 +2,34 @@ module UI module Windows - module_function - # Game catalog browser. Left=titles, right=card with story + links. - def games(app, services:) - BBS::Windows::MasterDetail.open(app, - title: ' Game Catalog ', - items: services[:games].all, - item_label: ->(g) { g.title }, - list_width: 30, - empty_message: ' (no games)', - render_detail: ->(game, width) { game_card_lines(game, width) } - ) - end - - def game_card_lines(game, width) - lines = [] - lines << " \e[1;33m#{game.title}\e[0m" - lines << " \e[0;36m#{game.platform} · #{game.author}\e[0m" - lines << '' - BBS::FrameBuffer.wrap_ansi(game.desc.to_s, width - 4).each { |l| lines << " #{l}" } - lines << '' - BBS::FrameBuffer.wrap_ansi(game.story.to_s, width - 4).each { |l| lines << " #{l}" } - lines << '' - game.external_links.each do |link| - lines << " \e[0;37m#{link[:label].ljust(10)}\e[0m \e[1;36m#{link[:url]}\e[0m" + class Games + def self.open(app, services:) + BBS::Windows::MasterDetail.open(app, + title: ' Game Catalog ', + items: services[:games].all, + item_label: ->(g) { g.title }, + list_width: 30, + empty_message: ' (no games)', + render_detail: ->(game, width) { game_card_lines(game, width) } + ) end - lines + + def self.game_card_lines(game, width) + lines = [] + lines << " \e[1;33m#{game.title}\e[0m" + lines << " \e[0;36m#{game.platform} · #{game.author}\e[0m" + lines << '' + BBS::FrameBuffer.wrap_ansi(game.desc.to_s, width - 4).each { |l| lines << " #{l}" } + lines << '' + BBS::FrameBuffer.wrap_ansi(game.story.to_s, width - 4).each { |l| lines << " #{l}" } + lines << '' + game.external_links.each do |link| + lines << " \e[0;37m#{link[:label].ljust(10)}\e[0m \e[1;36m#{link[:url]}\e[0m" + end + lines + end + private_class_method :game_card_lines end end end diff --git a/lib/ui/windows/messages_window.rb b/lib/ui/windows/messages_window.rb index 92de1c1..8e9d2e8 100644 --- a/lib/ui/windows/messages_window.rb +++ b/lib/ui/windows/messages_window.rb @@ -2,70 +2,73 @@ module UI module Windows - module_function - # Two-pane message reader: left=board list, right=messages of the selected # board. Press &New to compose, &Refresh to reload. - def messages(app, services:) - BBS::Windows::MasterDetail.open(app, - title: ' Message Boards ', - items: services[:boards].boards, - item_label: ->(b) { b.title }, - list_width: 20, - empty_message: ' (no boards configured)', - render_detail: ->(board, width) { board_lines(services, board, width) }, - actions: [ - { label: '&New', align: :left, on_click: ->(window) { - board = window.list_widget.selected_item - compose_message(app, services: services, board: board, - on_posted: -> { window.reload_detail }) - :keep - } }, - { label: '&Refresh', align: :left, on_click: ->(window) { - window.reload_detail - :keep - } } - ] - ) - end - - def board_lines(services, board, width) - msgs = services[:boards].messages(board.id, 100) - return [" (no messages on #{board.title} yet — press 'n' to start one)"] if msgs.empty? - lines = [] - msgs.each do |m| - head = "\e[2;37m#{m.timestamp}\e[0m \e[1;33m#{m.username}\e[0m: " - BBS::FrameBuffer.wrap_ansi(head + m.text.to_s, width - 1).each { |l| lines << l } - lines << '' + class Messages + def self.open(app, services:) + BBS::Windows::MasterDetail.open(app, + title: ' Message Boards ', + items: services[:boards].boards, + item_label: ->(b) { b.title }, + list_width: 20, + empty_message: ' (no boards configured)', + render_detail: ->(board, width) { board_lines(services, board, width) }, + actions: [ + { label: '&New', align: :left, on_click: ->(window) { + board = window.list_widget.selected_item + ComposeMessage.open(app, services: services, board: board, + on_posted: -> { window.reload_detail }) + :keep + } }, + { label: '&Refresh', align: :left, on_click: ->(window) { + window.reload_detail + :keep + } } + ] + ) end - lines + + def self.board_lines(services, board, width) + msgs = services[:boards].messages(board.id, 100) + return [" (no messages on #{board.title} yet — press 'n' to start one)"] if msgs.empty? + lines = [] + msgs.each do |m| + head = "\e[2;37m#{m.timestamp}\e[0m \e[1;33m#{m.username}\e[0m: " + BBS::FrameBuffer.wrap_ansi(head + m.text.to_s, width - 1).each { |l| lines << l } + lines << '' + end + lines + end + private_class_method :board_lines end - def compose_message(app, services:, board:, on_posted: nil) - return BBS::Dialogs.message(app, 'Select a board first.', title: ' Compose ') unless board + class ComposeMessage + def self.open(app, services:, board:, on_posted: nil) + return BBS::Dialogs.message(app, 'Select a board first.', title: ' Compose ') unless board - username = app.context[:username] || 'Anonymous' - width = [app.cols - 8, 70].min - BBS::Windows::Form.open(app, - title: " Post to #{board.title} ", - width: width, height: 14, - hint: "From: #{username} — Ctrl-Enter sends", - submit_label: '&Send', - cancel_label: '&Cancel', - fields: [ - { key: :body, type: :textarea, value: '', max_length: 1000 } - ], - on_submit: ->(values, form) { - result = services[:boards].post(board.id, username, values[:body]) - if result == :empty - form.error('Message cannot be empty.') - :keep - else - on_posted&.call - :close - end - } - ) + username = app.context[:username] || 'Anonymous' + width = [app.cols - 8, 70].min + BBS::Windows::Form.open(app, + title: " Post to #{board.title} ", + width: width, height: 14, + hint: "From: #{username} — Ctrl-Enter sends", + submit_label: '&Send', + cancel_label: '&Cancel', + fields: [ + { key: :body, type: :textarea, value: '', max_length: 1000 } + ], + on_submit: ->(values, form) { + result = services[:boards].post(board.id, username, values[:body]) + if result == :empty + form.error('Message cannot be empty.') + :keep + else + on_posted&.call + :close + end + } + ) + end end end end diff --git a/lib/ui/windows/profile_window.rb b/lib/ui/windows/profile_window.rb index 15a4b29..7127561 100644 --- a/lib/ui/windows/profile_window.rb +++ b/lib/ui/windows/profile_window.rb @@ -2,30 +2,30 @@ module UI module Windows - module_function - - def profile(app, services:) - username = app.context[:username] || 'Anonymous' - existing = services[:profile].find(username) || {} - BBS::Windows::Form.open(app, - title: " Profile — #{username} ", - width: 64, height: 14, - fields: [ - { key: :signature, label: 'Signature', value: existing['signature'].to_s }, - { key: :location, label: 'Location', value: existing['location'].to_s }, - { key: :homepage, label: 'Homepage', value: existing['homepage'].to_s }, - { key: :notes, label: 'Notes', value: existing['notes'].to_s } - ], - on_submit: ->(values, _form) { - services[:profile].update(username, - signature: values[:signature], - location: values[:location], - homepage: values[:homepage], - notes: values[:notes] - ) - BBS::Dialogs.message(app, 'Profile saved.', title: ' Saved ') - } - ) + class Profile + def self.open(app, services:) + username = app.context[:username] || 'Anonymous' + existing = services[:profile].find(username) || {} + BBS::Windows::Form.open(app, + title: " Profile — #{username} ", + width: 64, height: 14, + fields: [ + { key: :signature, label: 'Signature', value: existing['signature'].to_s }, + { key: :location, label: 'Location', value: existing['location'].to_s }, + { key: :homepage, label: 'Homepage', value: existing['homepage'].to_s }, + { key: :notes, label: 'Notes', value: existing['notes'].to_s } + ], + on_submit: ->(values, _form) { + services[:profile].update(username, + signature: values[:signature], + location: values[:location], + homepage: values[:homepage], + notes: values[:notes] + ) + BBS::Dialogs.message(app, 'Profile saved.', title: ' Saved ') + } + ) + end end end end diff --git a/lib/ui/windows/sysop_window.rb b/lib/ui/windows/sysop_window.rb index a0d2615..d82014f 100644 --- a/lib/ui/windows/sysop_window.rb +++ b/lib/ui/windows/sysop_window.rb @@ -2,41 +2,41 @@ module UI module Windows - module_function + class SysopConsole + def self.open(app, services:) + body = [] + body << " Sysop: \e[1;33m#{app.context[:username]}\e[0m" + body << :spacer + body << ' Stats' + body << " · Online users: #{services[:online].count}" + body << " · Total messages: #{services[:boards].total_count}" + body << " · Chat subscribers: #{services[:chat].subscriber_count}" + body << :spacer + body << ' Online users:' + services[:online].user_list.each { |u| body << " · #{u}" } + body << :spacer + body << ' Last 5 callers:' + services[:last_callers].recent(5).each do |c| + body << " · #{c.username} #{c.timestamp[0, 16].sub('T', ' ')}" + end - def sysop_console(app, services:) - body = [] - body << " Sysop: \e[1;33m#{app.context[:username]}\e[0m" - body << :spacer - body << ' Stats' - body << " · Online users: #{services[:online].count}" - body << " · Total messages: #{services[:boards].total_count}" - body << " · Chat subscribers: #{services[:chat].subscriber_count}" - body << :spacer - body << ' Online users:' - services[:online].user_list.each { |u| body << " · #{u}" } - body << :spacer - body << ' Last 5 callers:' - services[:last_callers].recent(5).each do |c| - body << " · #{c.username} #{c.timestamp[0, 16].sub('T', ' ')}" + BBS::Windows::Info.open(app, + title: ' Sysop Console ', width: 70, height: 18, scroll: true, + body: body, + buttons: [ + { label: '&Broadcast', align: :left, on_click: ->(_window) { + BBS::Dialogs.input(app, prompt: 'Sysop broadcast message:', + title: ' Broadcast ', width: 64, + on_submit: lambda do |text| + next if text.strip.empty? + services[:chat].broadcast("[SYSOP] #{app.context[:username]}", text.strip) + end) + :keep + } }, + { label: '&Close', cancel: true } + ] + ) end - - BBS::Windows::Info.open(app, - title: ' Sysop Console ', width: 70, height: 18, scroll: true, - body: body, - buttons: [ - { label: '&Broadcast', align: :left, on_click: ->(_window) { - BBS::Dialogs.input(app, prompt: 'Sysop broadcast message:', - title: ' Broadcast ', width: 64, - on_submit: lambda do |text| - next if text.strip.empty? - services[:chat].broadcast("[SYSOP] #{app.context[:username]}", text.strip) - end) - :keep - } }, - { label: '&Close', cancel: true } - ] - ) end end end diff --git a/lib/ui/windows/system_info_window.rb b/lib/ui/windows/system_info_window.rb index edff6f5..f29f8bb 100644 --- a/lib/ui/windows/system_info_window.rb +++ b/lib/ui/windows/system_info_window.rb @@ -2,32 +2,33 @@ module UI module Windows - module_function + class SystemInfo + def self.open(app, services:) + rows = [ + ['Online users', services[:online].count.to_s], + ['Messages', services[:boards].total_count.to_s], + ['Boards', services[:boards].boards.size.to_s], + ['Wiki', 'https://wiki.teletypegames.org'], + ['Games API', 'https://teletypegames.org'], + ['Platform', RUBY_PLATFORM], + ['Ruby', RUBY_VERSION], + ['Uptime', format_uptime(services[:uptime])], + ] + BBS::Windows::Info.open(app, + title: ' System Info ', width: 60, height: 14, + body: [{ kv: rows, label_width: 15 }] + ) + end - def system_info(app, services:) - rows = [ - ['Online users', services[:online].count.to_s], - ['Messages', services[:boards].total_count.to_s], - ['Boards', services[:boards].boards.size.to_s], - ['Wiki', 'https://wiki.teletypegames.org'], - ['Games API', 'https://teletypegames.org'], - ['Platform', RUBY_PLATFORM], - ['Ruby', RUBY_VERSION], - ['Uptime', format_uptime(services[:uptime])], - ] - BBS::Windows::Info.open(app, - title: ' System Info ', width: 60, height: 14, - body: [{ kv: rows, label_width: 15 }] - ) - end - - def format_uptime(start) - return '–' unless start - seconds = (Time.now - start).to_i - h = seconds / 3600 - m = (seconds % 3600) / 60 - s = seconds % 60 - "#{h}h #{m}m #{s}s" + def self.format_uptime(start) + return '–' unless start + seconds = (Time.now - start).to_i + h = seconds / 3600 + m = (seconds % 3600) / 60 + s = seconds % 60 + "#{h}h #{m}m #{s}s" + end + private_class_method :format_uptime end end end diff --git a/lib/ui/windows/users_window.rb b/lib/ui/windows/users_window.rb index e12258d..bb10460 100644 --- a/lib/ui/windows/users_window.rb +++ b/lib/ui/windows/users_window.rb @@ -2,38 +2,40 @@ module UI module Windows - module_function - - def online_users(app, services:) - users = services[:online].user_list - username = app.context[:username] - body = if users.empty? - [' (no one else here yet)'] - else - users.map { |u| u == username ? " #{u} ← you" : " #{u}" } - end - BBS::Windows::Info.open(app, - title: ' Online Users ', - width: 50, height: [users.size + 6, 10].max, - scroll: true, body: body - ) + class OnlineUsers + def self.open(app, services:) + users = services[:online].user_list + username = app.context[:username] + body = if users.empty? + [' (no one else here yet)'] + else + users.map { |u| u == username ? " #{u} ← you" : " #{u}" } + end + BBS::Windows::Info.open(app, + title: ' Online Users ', + width: 50, height: [users.size + 6, 10].max, + scroll: true, body: body + ) + end end - def last_callers(app, services:) - callers = services[:last_callers].recent(15) - body = if callers.empty? - [' (no callers recorded yet)'] - else - callers.map do |c| - ts = c.timestamp.to_s[0, 19].sub('T', ' ') - " \e[2;37m#{ts}\e[0m \e[1;33m#{c.username}\e[0m" + class LastCallers + def self.open(app, services:) + callers = services[:last_callers].recent(15) + body = if callers.empty? + [' (no callers recorded yet)'] + else + callers.map do |c| + ts = c.timestamp.to_s[0, 19].sub('T', ' ') + " \e[2;37m#{ts}\e[0m \e[1;33m#{c.username}\e[0m" + end end - end - BBS::Windows::Info.open(app, - title: ' Last Callers ', - width: 64, height: [callers.size + 6, 10].max, - scroll: true, body: body - ) + BBS::Windows::Info.open(app, + title: ' Last Callers ', + width: 64, height: [callers.size + 6, 10].max, + scroll: true, body: body + ) + end end end end diff --git a/lib/ui/windows/wiki_window.rb b/lib/ui/windows/wiki_window.rb index bc3d62c..587d8d6 100644 --- a/lib/ui/windows/wiki_window.rb +++ b/lib/ui/windows/wiki_window.rb @@ -2,27 +2,27 @@ module UI module Windows - module_function - # Split-view wiki browser. Left=titles, right=selected page content # (rendered with markdown-aware formatting). - def wiki(app, services:, category:, title:) - items = services[:wiki].list(category) - BBS::Windows::MasterDetail.open(app, - title: " #{title} ", - items: items, - item_label: ->(p) { p.title }, - list_width: [32, app.cols / 3].max, - empty_message: ' (no pages found)', - close_button: false, - render_detail: ->(page, width) { - text = services[:wiki].content(page.id) - BBS::Markdown.to_lines(text, width: width - 1, theme: app.theme) - }, - bottom_meta: ->(page) { - "#{page.created_at.to_s[0, 10]} #{services[:wiki].page_url(page.locale, page.path)}" - } - ) + class Wiki + def self.open(app, services:, category:, title:) + items = services[:wiki].list(category) + BBS::Windows::MasterDetail.open(app, + title: " #{title} ", + items: items, + item_label: ->(p) { p.title }, + list_width: [32, app.cols / 3].max, + empty_message: ' (no pages found)', + close_button: false, + render_detail: ->(page, width) { + text = services[:wiki].content(page.id) + BBS::Markdown.to_lines(text, width: width - 1, theme: app.theme) + }, + bottom_meta: ->(page) { + "#{page.created_at.to_s[0, 10]} #{services[:wiki].page_url(page.locale, page.path)}" + } + ) + end end end end