refact round 3

This commit is contained in:
2026-03-11 20:42:07 +01:00
parent d843df816a
commit 2157425b24
15 changed files with 477 additions and 413 deletions

45
content/data.catalog.go Normal file
View File

@@ -0,0 +1,45 @@
package content
import (
"encoding/json"
"net/http"
"time"
)
const GamesAPIURL = "https://games.teletype.hu/api/software"
type softwareEntry struct {
Software struct {
Title string `json:"title"`
Platform string `json:"platform"`
Author string `json:"author"`
Desc string `json:"desc"`
} `json:"software"`
Releases []interface{} `json:"releases"`
LatestRelease *struct {
Version string `json:"version"`
HTMLFolderPath string `json:"htmlFolderPath"`
CartridgePath string `json:"cartridgePath"`
SourcePath string `json:"sourcePath"`
DocsFolderPath string `json:"docsFolderPath"`
} `json:"latestRelease"`
}
type catalogRepository struct{}
func (r *catalogRepository) fetchGames() ([]softwareEntry, error) {
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Get(GamesAPIURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Softwares []softwareEntry `json:"softwares"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result.Softwares, nil
}

66
content/data.message.go Normal file
View File

@@ -0,0 +1,66 @@
package content
import (
"encoding/csv"
"os"
"sync"
)
type message struct {
Timestamp string
User string
Text string
}
// MessageBoard holds message board data
type MessageBoard struct {
messages []message
mu sync.Mutex
path string
}
// NewMessageBoard loads messages from the given .dat file
func NewMessageBoard(path string) *MessageBoard {
b := &MessageBoard{path: path}
b.load()
return b
}
func (b *MessageBoard) load() {
f, err := os.Open(b.path)
if err != nil {
return
}
defer f.Close()
r := csv.NewReader(f)
records, err := r.ReadAll()
if err != nil {
return
}
for _, row := range records {
if len(row) != 3 {
continue
}
b.messages = append(b.messages, message{Timestamp: row[0], User: row[1], Text: row[2]})
}
}
func (b *MessageBoard) append(msg message) error {
f, err := os.OpenFile(b.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
w := csv.NewWriter(f)
err = w.Write([]string{msg.Timestamp, msg.User, msg.Text})
w.Flush()
return err
}
func (b *MessageBoard) Count() int {
b.mu.Lock()
defer b.mu.Unlock()
return len(b.messages)
}

102
content/data.wiki.go Normal file
View File

@@ -0,0 +1,102 @@
package content
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const WikiJSBaseURL = "https://wiki.teletype.hu"
type wikiPage struct {
ID interface{} `json:"id"`
Path string `json:"path"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Locale string `json:"locale"`
}
type wikiRepository struct {
token string
}
func (r *wikiRepository) graphql(query string) (map[string]interface{}, error) {
payload := map[string]string{"query": query}
data, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", WikiJSBaseURL+"/graphql", bytes.NewBuffer(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if r.token != "" {
req.Header.Set("Authorization", "Bearer "+r.token)
}
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
func (r *wikiRepository) fetchList(tag string) ([]wikiPage, error) {
q := fmt.Sprintf(`{ pages { list(orderBy: CREATED, orderByDirection: DESC, tags: ["%s"]) { id path title description createdAt updatedAt locale } } }`, tag)
res, err := r.graphql(q)
if err != nil {
return nil, err
}
data, ok := res["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
pages, ok := data["pages"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
listRaw, ok := pages["list"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
var list []wikiPage
jsonBytes, _ := json.Marshal(listRaw)
json.Unmarshal(jsonBytes, &list)
return list, nil
}
func (r *wikiRepository) fetchContent(pageID interface{}) (string, error) {
var idStr string
switch v := pageID.(type) {
case string:
idStr = v
case float64:
idStr = fmt.Sprintf("%.0f", v)
default:
idStr = fmt.Sprintf("%v", v)
}
q := fmt.Sprintf(`{ pages { single(id: %s) { content } } }`, idStr)
res, err := r.graphql(q)
if err != nil {
return "", err
}
data := res["data"].(map[string]interface{})
pages := data["pages"].(map[string]interface{})
single := pages["single"].(map[string]interface{})
return single["content"].(string), nil
}

108
content/handler.blog.go Normal file
View File

@@ -0,0 +1,108 @@
package content
import (
"bbs-server/engine"
"fmt"
"strconv"
"strings"
)
// BlogHandler handles the Blog Posts menu item
type BlogHandler struct {
repo *wikiRepository
}
func NewBlogHandler(token string) *BlogHandler {
return &BlogHandler{repo: &wikiRepository{token: token}}
}
func (h *BlogHandler) Handle(s *engine.Session) {
renderWikiList(s, h.repo, "blog", "Blog Posts", engine.BL)
}
// renderWikiList is a helper used by wiki-based handlers
func renderWikiList(s *engine.Session, repo *wikiRepository, tag, title, color string) {
s.Printer.BoxHeader(title, color)
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["WikiLoading"], engine.R))
pages, err := repo.fetchList(tag)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%s%s: %v%s\r\n", engine.RD, s.Lang["WikiConnError"], err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
if len(pages) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, fmt.Sprintf(s.Lang["WikiNoResults"], tag), engine.R))
s.Printer.Pause(s.Lang)
return
}
maxIdx := 25
if len(pages) < maxIdx {
maxIdx = len(pages)
}
for i, p := range pages[:maxIdx] {
date := s.Printer.FmtDate(p.CreatedAt)
if date == "" {
date = s.Printer.FmtDate(p.UpdatedAt)
}
titleP := p.Title
if titleP == "" {
titleP = p.Path
}
if len(titleP) > 55 {
titleP = titleP[:55]
}
desc := p.Description
if len(desc) > 60 {
desc = desc[:60]
}
s.Printer.Send(fmt.Sprintf(" %s%2d%s %s%s%s\r\n", color, i+1, engine.R, engine.WH, titleP, engine.R))
if desc != "" {
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, date, engine.DIM, desc, engine.R))
} else {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, date, engine.R))
}
}
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.GY, s.Lang["WikiEnterNum"], engine.R))
choice, _ := s.Printer.ReadLine()
choice = strings.TrimSpace(choice)
idx, err := strconv.Atoi(choice)
if err != nil || idx < 1 || idx > len(pages) {
return
}
page := pages[idx-1]
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s", engine.GY, s.Lang["WikiFetchContent"], engine.R))
pageContent, err := repo.fetchContent(page.ID)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%sHiba: %v%s\r\n", engine.RD, err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
s.Printer.Send("\r\n")
s.Printer.HR("═", color)
s.Printer.Send("\r\n")
s.Printer.Send(fmt.Sprintf(" %s%s%s%s\r\n", engine.WH, engine.B, page.Title, engine.R))
url := fmt.Sprintf("%s/%s/%s", WikiJSBaseURL, page.Locale, page.Path)
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, s.Printer.FmtDate(page.CreatedAt), engine.DIM, url, engine.R))
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n\r\n")
body := s.Printer.Wrapped(pageContent, 2, 5000)
for _, line := range strings.Split(body, "\r\n") {
s.Printer.Send(line + "\r\n")
}
s.Printer.Send("\r\n")
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n")
s.Printer.Pause(s.Lang)
}

View File

@@ -2,52 +2,11 @@ package content
import (
"bbs-server/engine"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
const GamesAPIURL = "https://games.teletype.hu/api/software"
type softwareEntry struct {
Software struct {
Title string `json:"title"`
Platform string `json:"platform"`
Author string `json:"author"`
Desc string `json:"desc"`
} `json:"software"`
Releases []interface{} `json:"releases"`
LatestRelease *struct {
Version string `json:"version"`
HTMLFolderPath string `json:"htmlFolderPath"`
CartridgePath string `json:"cartridgePath"`
SourcePath string `json:"sourcePath"`
DocsFolderPath string `json:"docsFolderPath"`
} `json:"latestRelease"`
}
type catalogRepository struct{}
func (r *catalogRepository) fetchGames() ([]softwareEntry, error) {
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Get(GamesAPIURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Softwares []softwareEntry `json:"softwares"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result.Softwares, nil
}
// CatalogHandler renders the game catalog
// CatalogHandler handles the Game Catalog menu item
type CatalogHandler struct {
repo *catalogRepository
}
@@ -56,8 +15,7 @@ func NewCatalogHandler() *CatalogHandler {
return &CatalogHandler{repo: &catalogRepository{}}
}
// Show displays the game catalog
func (h *CatalogHandler) Show(s *engine.Session) {
func (h *CatalogHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["CatTitle"], engine.YL)
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["WikiLoading"], engine.R))

8
content/handler.go Normal file
View File

@@ -0,0 +1,8 @@
package content
import "bbs-server/engine"
// Handler is the common interface for all BBS menu handlers
type Handler interface {
Handle(s *engine.Session)
}

16
content/handler.howtos.go Normal file
View File

@@ -0,0 +1,16 @@
package content
import "bbs-server/engine"
// HowToHandler handles the HowTo Guides menu item
type HowToHandler struct {
repo *wikiRepository
}
func NewHowToHandler(token string) *HowToHandler {
return &HowToHandler{repo: &wikiRepository{token: token}}
}
func (h *HowToHandler) Handle(s *engine.Session) {
renderWikiList(s, h.repo, "howto", "HowTo Guides", engine.MG)
}

View File

@@ -0,0 +1,40 @@
package content
import (
"bbs-server/engine"
"fmt"
)
// MessageBoardIndexHandler displays the message board posts
type MessageBoardIndexHandler struct {
board *MessageBoard
}
func NewMessageBoardIndexHandler(board *MessageBoard) *MessageBoardIndexHandler {
return &MessageBoardIndexHandler{board: board}
}
func (h *MessageBoardIndexHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["MsgBoardTitle"], engine.GR)
h.board.mu.Lock()
snap := make([]message, len(h.board.messages))
copy(snap, h.board.messages)
h.board.mu.Unlock()
if len(snap) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["MsgNoMessages"], engine.R))
} else {
start := 0
if len(snap) > 30 {
start = len(snap) - 30
}
for i, msg := range snap[start:] {
s.Printer.Send(fmt.Sprintf(" %s%02d%s %s%s%s %s%s:%s %s\r\n",
engine.GR, i+1, engine.R,
engine.GY, msg.Timestamp, engine.R,
engine.WH, msg.User, engine.R, msg.Text))
}
}
s.Printer.Pause(s.Lang)
}

View File

@@ -0,0 +1,38 @@
package content
import (
"bbs-server/engine"
"fmt"
"strings"
"time"
)
// MessageBoardNewHandler handles posting new messages to the board
type MessageBoardNewHandler struct {
board *MessageBoard
}
func NewMessageBoardNewHandler(board *MessageBoard) *MessageBoardNewHandler {
return &MessageBoardNewHandler{board: board}
}
func (h *MessageBoardNewHandler) Handle(s *engine.Session) {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.WH, s.Lang["MsgEnterText"], engine.R))
msgText, _ := s.Printer.ReadLine()
msgText = strings.TrimSpace(msgText)
if msgText != "" {
if len(msgText) > 200 {
msgText = msgText[:200]
}
ts := time.Now().Format("01-02 15:04")
msg := message{Timestamp: ts, User: s.Username, Text: msgText}
h.board.mu.Lock()
h.board.messages = append(h.board.messages, msg)
h.board.mu.Unlock()
h.board.append(msg)
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GR, s.Lang["MsgSent"], engine.R))
} else {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GY, s.Lang["MsgEmpty"], engine.R))
}
s.Printer.Pause(s.Lang)
}

View File

@@ -6,8 +6,14 @@ import (
"sort"
)
// Online displays currently logged-in users
func Online(s *engine.Session) {
// OnlineHandler displays currently logged-in users
type OnlineHandler struct{}
func NewOnlineHandler() *OnlineHandler {
return &OnlineHandler{}
}
func (h *OnlineHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["OnlineTitle"], engine.CY)
snap := s.State.Snapshot()

View File

@@ -0,0 +1,27 @@
package content
import (
"bbs-server/engine"
"fmt"
"runtime"
)
// SysinfoHandler displays system information
type SysinfoHandler struct {
board *MessageBoard
}
func NewSysinfoHandler(board *MessageBoard) *SysinfoHandler {
return &SysinfoHandler{board: board}
}
func (h *SysinfoHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["SysTitle"], engine.GY)
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %d\r\n", engine.GY, s.Lang["SysUsers"], engine.WH, s.State.UserCount()))
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %d\r\n", engine.GY, s.Lang["SysMessages"], engine.WH, h.board.Count()))
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %s\r\n", engine.GY, s.Lang["SysOS"], engine.WH, runtime.GOOS))
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %s\r\n", engine.GY, s.Lang["SysArch"], engine.WH, runtime.GOARCH))
s.Printer.Pause(s.Lang)
}

View File

@@ -1,121 +0,0 @@
package content
import (
"bbs-server/engine"
"encoding/csv"
"fmt"
"os"
"strings"
"sync"
"time"
)
type message struct {
Timestamp string
User string
Text string
}
// MessageBoard holds message board data and its handler
type MessageBoard struct {
messages []message
mu sync.Mutex
path string
}
// NewMessageBoard loads messages from the given .dat file (if it exists)
func NewMessageBoard(path string) *MessageBoard {
b := &MessageBoard{path: path}
b.load()
return b
}
func (b *MessageBoard) load() {
f, err := os.Open(b.path)
if err != nil {
return // file does not exist yet, start empty
}
defer f.Close()
r := csv.NewReader(f)
records, err := r.ReadAll()
if err != nil {
return
}
for _, row := range records {
if len(row) != 3 {
continue
}
b.messages = append(b.messages, message{Timestamp: row[0], User: row[1], Text: row[2]})
}
}
func (b *MessageBoard) append(msg message) error {
f, err := os.OpenFile(b.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
w := csv.NewWriter(f)
err = w.Write([]string{msg.Timestamp, msg.User, msg.Text})
w.Flush()
return err
}
// Count returns the number of messages in a thread-safe manner
func (b *MessageBoard) Count() int {
b.mu.Lock()
defer b.mu.Unlock()
return len(b.messages)
}
// Show displays the message board and handles new message input
func (b *MessageBoard) Show(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["MsgBoardTitle"], engine.GR)
b.mu.Lock()
snap := make([]message, len(b.messages))
copy(snap, b.messages)
b.mu.Unlock()
if len(snap) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["MsgNoMessages"], engine.R))
} else {
start := 0
if len(snap) > 30 {
start = len(snap) - 30
}
for i, msg := range snap[start:] {
s.Printer.Send(fmt.Sprintf(" %s%02d%s %s%s%s %s%s:%s %s\r\n",
engine.GR, i+1, engine.R,
engine.GY, msg.Timestamp, engine.R,
engine.WH, msg.User, engine.R, msg.Text))
}
}
s.Printer.Send(fmt.Sprintf("\r\n%s[N]%s %s %s[ENTER]%s %s → ",
engine.GY, engine.R, s.Lang["MsgNew"],
engine.GY, engine.R, s.Lang["MsgBack"]))
choice, _ := s.Printer.ReadLine()
if strings.ToUpper(strings.TrimSpace(choice)) == "N" {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.WH, s.Lang["MsgEnterText"], engine.R))
msgText, _ := s.Printer.ReadLine()
msgText = strings.TrimSpace(msgText)
if msgText != "" {
if len(msgText) > 200 {
msgText = msgText[:200]
}
ts := time.Now().Format("01-02 15:04")
msg := message{Timestamp: ts, User: s.Username, Text: msgText}
b.mu.Lock()
b.messages = append(b.messages, msg)
b.mu.Unlock()
b.append(msg)
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GR, s.Lang["MsgSent"], engine.R))
} else {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GY, s.Lang["MsgEmpty"], engine.R))
}
}
}

View File

@@ -1,32 +0,0 @@
package content
import (
"bbs-server/engine"
"fmt"
"strconv"
"time"
)
// Sysinfo returns a HandlerFunc that displays system information
func Sysinfo(board *MessageBoard) engine.HandlerFunc {
return func(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["SysInfoTitle"], engine.GY)
now := time.Now().Format("2006-01-02 15:04:05")
rows := [][]string{
{s.Lang["SysServerTime"], now},
{s.Lang["SysOnlineUsers"], strconv.Itoa(s.State.UserCount())},
{s.Lang["SysMsgCount"], strconv.Itoa(board.Count())},
{s.Lang["SysWikiURL"], WikiJSBaseURL},
{s.Lang["SysGamesAPI"], GamesAPIURL},
{s.Lang["SysPlatform"], "Go BBS v2.0"},
}
for _, row := range rows {
s.Printer.Send(fmt.Sprintf(" %s%-18s%s %s%s%s\r\n",
engine.GY, row[0], engine.R, engine.WH, row[1], engine.R))
}
s.Printer.Pause(s.Lang)
}
}

View File

@@ -1,203 +0,0 @@
package content
import (
"bbs-server/engine"
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
const WikiJSBaseURL = "https://wiki.teletype.hu"
type wikiPage struct {
ID interface{} `json:"id"`
Path string `json:"path"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Locale string `json:"locale"`
}
type wikiRepository struct {
token string
}
func (r *wikiRepository) graphql(query string) (map[string]interface{}, error) {
payload := map[string]string{"query": query}
data, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", WikiJSBaseURL+"/graphql", bytes.NewBuffer(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if r.token != "" {
req.Header.Set("Authorization", "Bearer "+r.token)
}
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
func (r *wikiRepository) fetchList(tag string) ([]wikiPage, error) {
q := fmt.Sprintf(`{ pages { list(orderBy: CREATED, orderByDirection: DESC, tags: ["%s"]) { id path title description createdAt updatedAt locale } } }`, tag)
res, err := r.graphql(q)
if err != nil {
return nil, err
}
data, ok := res["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
pages, ok := data["pages"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
listRaw, ok := pages["list"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
var list []wikiPage
jsonBytes, _ := json.Marshal(listRaw)
json.Unmarshal(jsonBytes, &list)
return list, nil
}
func (r *wikiRepository) fetchContent(pageID interface{}) (string, error) {
var idStr string
switch v := pageID.(type) {
case string:
idStr = v
case float64:
idStr = fmt.Sprintf("%.0f", v)
default:
idStr = fmt.Sprintf("%v", v)
}
q := fmt.Sprintf(`{ pages { single(id: %s) { content } } }`, idStr)
res, err := r.graphql(q)
if err != nil {
return "", err
}
data := res["data"].(map[string]interface{})
pages := data["pages"].(map[string]interface{})
single := pages["single"].(map[string]interface{})
return single["content"].(string), nil
}
// WikiHandler renders Wiki.js content in the BBS
type WikiHandler struct {
repo *wikiRepository
}
func NewWikiHandler(token string) *WikiHandler {
return &WikiHandler{repo: &wikiRepository{token: token}}
}
// List returns a HandlerFunc that lists pages with the given tag
func (h *WikiHandler) List(tag, title, color string) engine.HandlerFunc {
return func(s *engine.Session) {
s.Printer.BoxHeader(title, color)
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["WikiLoading"], engine.R))
pages, err := h.repo.fetchList(tag)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%s%s: %v%s\r\n", engine.RD, s.Lang["WikiConnError"], err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
if len(pages) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, fmt.Sprintf(s.Lang["WikiNoResults"], tag), engine.R))
s.Printer.Pause(s.Lang)
return
}
maxIdx := 25
if len(pages) < maxIdx {
maxIdx = len(pages)
}
for i, p := range pages[:maxIdx] {
date := s.Printer.FmtDate(p.CreatedAt)
if date == "" {
date = s.Printer.FmtDate(p.UpdatedAt)
}
titleP := p.Title
if titleP == "" {
titleP = p.Path
}
if len(titleP) > 55 {
titleP = titleP[:55]
}
desc := p.Description
if len(desc) > 60 {
desc = desc[:60]
}
s.Printer.Send(fmt.Sprintf(" %s%2d%s %s%s%s\r\n", color, i+1, engine.R, engine.WH, titleP, engine.R))
if desc != "" {
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, date, engine.DIM, desc, engine.R))
} else {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, date, engine.R))
}
}
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.GY, s.Lang["WikiEnterNum"], engine.R))
choice, _ := s.Printer.ReadLine()
choice = strings.TrimSpace(choice)
idx, err := strconv.Atoi(choice)
if err != nil || idx < 1 || idx > len(pages) {
return
}
page := pages[idx-1]
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s", engine.GY, s.Lang["WikiFetchContent"], engine.R))
pageContent, err := h.repo.fetchContent(page.ID)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%sHiba: %v%s\r\n", engine.RD, err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
s.Printer.Send("\r\n")
s.Printer.HR("═", color)
s.Printer.Send("\r\n")
s.Printer.Send(fmt.Sprintf(" %s%s%s%s\r\n", engine.WH, engine.B, page.Title, engine.R))
url := fmt.Sprintf("%s/%s/%s", WikiJSBaseURL, page.Locale, page.Path)
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, s.Printer.FmtDate(page.CreatedAt), engine.DIM, url, engine.R))
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n\r\n")
body := s.Printer.Wrapped(pageContent, 2, 5000)
for _, line := range strings.Split(body, "\r\n") {
s.Printer.Send(line + "\r\n")
}
s.Printer.Send("\r\n")
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n")
s.Printer.Pause(s.Lang)
}
}