Skip to content
Snippets Groups Projects
Commit 93c6410c authored by echicken's avatar echicken :chicken:
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #6560 canceled
{
"extends": "./node_modules/@swag/ts4s/.babelrc.json"
}
\ No newline at end of file
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "echo source /usr/share/bash-completion/completions/git >> /home/node/.bashrc"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
.env
.tmp
node_modules
build/**/*
!build/nodelist-browser.js
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
{
"name": "nodelist-browser",
"version": "2.0.0",
"description": "",
"main": "src/nodelist-browser.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "ts4s build -s src -b build",
"deploy": "npm run build && eval $(cat .env) && scp -P $SSH_PORT build/*.js $SSH_USER@$SSH_HOST:$DEPLOY_PATH"
},
"author": "echicken",
"license": "MIT",
"dependencies": {
"@swag/ts4s": "git+ssh://git@gitlab.synchro.net:swag/ts4s.git",
"swindows": "git+ssh://git@gitlab.synchro.net:echicken/swindows.git"
}
}
import type { IFidoSysCfg } from '@swag/ts4s';
import type { ISettings } from './settings';
import { load } from '@swag/ts4s';
import { WindowManager } from "swindows";
import { IPosition, ISize } from "swindows/src/types";
import { ILightBarItem } from 'swindows/src/LightBar';
import Menu from './Menu';
type onSelect = (domain: string, nodeListFile: string) => void;
const fidoSysCfg: IFidoSysCfg = load('fido_syscfg.js');
const { file_exists, system, File } = js.global;
// Get the domain name as specified in sbbsecho.ini
const f = new File(`${system.ctrl_dir}sbbsecho.ini`);
f.open('r');
const domains = f.iniGetSections('domain:');
f.close();
const domainNames: Record<string, string> = {};
for (const domain of domains) {
const d = domain.substring(7);
domainNames[d.toLowerCase()] = d;
}
export default class DomainMenu extends Menu {
onSelect: onSelect;
settings: ISettings;
constructor(wm: WindowManager, position: IPosition, size: ISize, onSelect: onSelect, settings: ISettings) {
super('Nodelists', wm, position, size);
this.onSelect = onSelect;
this.settings = settings;
this.addItems();
}
getItems(): ILightBarItem[] {
const items: ILightBarItem[] = [];
const ftnDomains = new fidoSysCfg.FTNDomains();
for (const fn in ftnDomains.nodeListFN) {
if (!file_exists(ftnDomains.nodeListFN[fn] as string)) continue;
const domainName = this.settings.domains?.[domainNames[fn]] === undefined ? domainNames[fn] : this.settings.domains[domainNames[fn]];
items.push({
text: domainName,
onSelect: () => this.onSelect(domainName, ftnDomains.nodeListFN[fn] as string),
});
}
for (const nl in this.settings.nodelists) {
if (!file_exists(this.settings.nodelists[nl])) continue;
items.push({
text: nl,
onSelect: () => {
if (this.settings.nodelists !== undefined) {
this.onSelect(nl, this.settings.nodelists[nl]);
}
},
});
}
return items;
}
}
import * as swindows from 'swindows';
import { border, title } from './window-options';
export default class Help {
window: swindows.ControlledWindow;
constructor(wm: swindows.WindowManager, position: swindows.types.IPosition, size: swindows.types.ISize) {
const options = {
border,
title,
position,
size,
windowManager: wm,
name: `Help window`,
}
options.title.text = 'Help';
this.window = new swindows.ControlledWindow(options);
this.window.write(
'\r\n\x01h\x01wUse your \x01cUP\x01w/\x01cDOWN \x01wkeys to navigate the \x01cNodelist\x01w, \x01cZone\x01w, \x01cNet\x01w, and \x01cNode\x01w menus.\r\n\r\n'
+ 'On most screens, hit \x01cQ\x01w, \x01cC\x01w, \x01cbackspace\x01w, or \x01cESC\x01w to go back.\r\n\r\n'
+ 'Use \x01cCTRL-S \x01wto bring up the \x01cSearch \x01wscreen.\r\nHit \x01cENTER \x01win an empty input box to cancel a search.\r\n'
+ '\x01wUse your \x01cUP\x01w/\x01cDOWN \x01wkeys, or type to navigate the \x01csearch results \x01wmenu.\r\n'
+ 'Hit \x01cQ\x01w, \x01cC\x01w, \x01cbackspace\x01w, or \x01cESC\x01w to return to the search input.\r\n\r\n'
+ 'Use \x01cCTRL-Q \x01wat any time to \x01cquit\x01w.'
);
}
getCmd(cmd: string) {}
close() {
this.window.close();
}
}
\ No newline at end of file
import type { ICgaDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import * as swindows from 'swindows';
import { ILightBarItem } from 'swindows/src/LightBar';
import { border, title } from './window-options';
const cgadefs: ICgaDefs = load('cga_defs.js');
export default abstract class Menu {
name: string;
window: swindows.types.IControlledWindow;
windowManager: swindows.WindowManager;
menu: swindows.LightBar;
constructor(name: string, windowManager: swindows.WindowManager, position: swindows.types.IPosition, size: swindows.types.ISize, footer?: swindows.types.IBorderText) {
this.name = name;
this.windowManager = windowManager;
const options = {
border,
title,
footer,
position,
size,
scrollBar: {
vertical: {
enabled: true,
}
},
windowManager,
name: `${name} window`,
}
options.title.text = name;
this.window = new swindows.ControlledWindow(options);
this.menu = new swindows.LightBar({
items: [],
name: `${name} LightBar`,
window: this.window,
});
this.window.write('\x01h\x01wLoading \x01h\x01c...');
windowManager.refresh();
}
protected getItems(): ILightBarItem[] {
return [];
}
protected addItems(): void {
const items = this.getItems();
for (const item of items) {
this.menu.addItem(item);
}
this.menu.draw();
}
getCmd(cmd: string): void {
this.menu.getCmd(cmd);
}
close() {
this.window.remove();
}
}
\ No newline at end of file
import type { IFtnNodeList } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import { WindowManager } from "swindows";
import { IPosition, ISize } from "swindows/src/types";
import Menu from './Menu';
import { ILightBarItem } from 'swindows/src/LightBar';
type onSelect = (domain: string, zone: string, net: string, nodeListFile: string) => void;
const ftnNodeList: IFtnNodeList = load('ftn_nodelist.js');
export default class NetMenu extends Menu {
domain: string;
zone: string;
nodeListFile: string;
onSelect: onSelect;
constructor(wm: WindowManager, position: IPosition, size: ISize, domain: string, zone: string, nodeListFile: string, onSelect: onSelect) {
super(`${domain}, Zone ${zone}`, wm, position, size);
this.domain = domain;
this.zone = zone;
this.nodeListFile = nodeListFile;
this.onSelect = onSelect;
this.addItems();
}
getItems(): ILightBarItem[] {
const items: ILightBarItem[] = [];
const nodeList = new ftnNodeList.NodeList(this.nodeListFile, false);
nodeList.entries.sort((a, b) => {
const an = a.addr.match(/\d+:(\d+)\//);
const bn = b.addr.match(/\d+:(\d+)\//);
if (an === null || bn === null) return 0;
return parseInt(an[1], 10) - parseInt(bn[1], 10);
});
const nets: string[] = [];
for (const entry of nodeList.entries) {
const zn = entry.addr.match(/(\d+):(\d+)\//);
if (zn === null) continue;
if (zn[1] != this.zone) continue;
if (nets.indexOf(zn[2]) > -1) continue;
nets.push(zn[2]);
items.push({
text: `Net ${zn[2]}`,
onSelect: () => this.onSelect(this.domain, this.zone, zn[2], this.nodeListFile),
});
}
return items;
}
}
import { IFtnNodeList, ISbbsDefs, load } from '@swag/ts4s';
import * as swindows from 'swindows';
import { border, title } from './window-options';
const sbbsdefs: ISbbsDefs = load('sbbsdefs.js');
const { format, bbs, console } = js.global;
export default class NodeInfo {
windowManager: swindows.WindowManager;
window: swindows.ControlledWindow;
node: IFtnNodeList['Node'];
constructor(windowManager: swindows.WindowManager, position: swindows.types.IPosition, size: swindows.types.ISize, domain: string, zone: string, net: string, node: IFtnNodeList['Node']) {
this.windowManager = windowManager;
this.node = node;
const options = {
border,
title,
footer: {
text: '\x01h\x01cS\x01wend netmail\x01n\x01c, \x01hC\x01wlose',
attr: title.attr,
alignment: swindows.defs.ALIGNMENT.RIGHT,
},
position,
size,
windowManager,
name: `NodeInfo window`,
}
options.title.text = 'Node details';
this.window = new swindows.ControlledWindow(options);
const fmt = '\x01h\x01w%10s\x01n\x01c: \x01h\x01c%s\r\n';
this.window.write(format(fmt, 'Address', node.addr));
this.window.write(format(fmt, 'Name', node.name));
this.window.write(format(fmt, 'Sysop', node.sysop));
this.window.write(format(fmt, 'Location', node.location));
this.window.write(format(fmt, 'Hub', node.hub));
if (node.flags.INA !== undefined || node.flags.IP !== undefined) {
this.window.write(format(fmt, 'Internet', node.flags.INA ?? node.flags.IP));
}
const protocol = [];
if (node.flags.IBN !== undefined) protocol.push('Binkp');
if (node.flags.IFC !== undefined) protocol.push('ifcico');
if (node.flags.ITN !== undefined) protocol.push('Telnet');
if (node.flags.IVM !== undefined) protocol.push('Vmodem');
if (protocol.length) {
this.window.write(format(fmt, 'Protocol', protocol.join(', ')));
}
const email = [];
if (node.flags.IEM !== undefined) email.push(node.flags.IEM);
if (node.flags.ITX !== undefined) email.push('TransX');
if (node.flags.IUC !== undefined) email.push('UUEncode');
if (node.flags.IMI !== undefined) email.push('MIME');
if (node.flags.ISE !== undefined) email.push('SEAT');
if (email.length) this.window.write(format(fmt, 'Email', email.join(', ')));
if (node.flags.PING !== undefined) this.window.write(format(fmt, 'Accepts', 'PING'));
const other = [
'ZEC', 'REC', 'NEC', 'NC', 'SDS', 'SMH', 'RPK', 'NPK', 'ENC', 'CDP'
].filter(e => node.flags[e] !== undefined);
if (other.length) this.window.write(format(fmt, 'Flags', other.join(', ')));
const status = [];
if (node.private) status.push('Private');
if (node.hold) status.push('Hold');
if (node.down) status.push('Down');
if (status.length) this.window.write(format(fmt, 'Status', status.join(', ')));
}
getCmd(str: string): void {
if (str === 's') {
console.clear(0x200|7);
bbs.netmail(`${this.node.sysop}@${this.node.addr}`, sbbsdefs.WM_NETMAIL);
console.clear(0x200|7);
this.windowManager.draw();
}
}
close() {
this.window.remove();
}
}
import { IFtnNodeList } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import { WindowManager } from "swindows";
import { IPosition, ISize } from "swindows/src/types";
import Menu from './Menu';
import { ILightBarItem } from 'swindows/src/LightBar';
const ftnNodeList: IFtnNodeList = load('ftn_nodelist.js');
const { format } = js.global;
type onSelect = (domain: string, zone: string, net: string, node: IFtnNodeList['Node'], nodeListFile: string) => void;
export default class NodeMenu extends Menu {
domain: string;
zone: string;
net: string;
nodeListFile: string;
onSelect: onSelect;
constructor(wm: WindowManager, position: IPosition, size: ISize, domain: string, zone: string, net: string, nodeListFile: string, onSelect: onSelect) {
super(`${domain}, Zone ${zone}, Net ${net}`, wm, position, size);
this.domain = domain;
this.zone = zone;
this.net = net;
this.nodeListFile = nodeListFile;
this.onSelect = onSelect;
this.addItems();
}
getItems(): ILightBarItem[] {
const w = Math.floor((this.window.size.width - 2 /* borders */ - 1 /* scrollbar */ - 1 /* space */ - 15 /* addr w/space */) / 3 /* sysop, name, location */);
const w1 = w - 1;
const fmt = `%-15s%-${w}s%-${w}s%-${w}s`;
const items: ILightBarItem[] = [];
const nodeList = new ftnNodeList.NodeList(this.nodeListFile, false);
nodeList.entries.sort((a, b) => {
return parseInt(a.addr.split('/')[1], 10) - parseInt(b.addr.split('/')[1], 10);
});
for (const entry of nodeList.entries) {
const z = entry.addr.split(':')[0];
if (z !== this.zone) continue;
const n = entry.addr.match(/\d+:(\d+)\//);
if (n === null || n[1] !== this.net) continue;
items.push({
text: format(fmt, entry.addr, entry.sysop.substring(0, w1), entry.name.substring(0, w1), entry.location.substring(0, w1)),
onSelect: () => this.onSelect(this.domain, this.zone, this.net, entry, this.nodeListFile),
});
}
return items;
}
}
import { ICgaDefs, IFidoSysCfg, IFtnNodeList, load } from '@swag/ts4s';
import * as swindows from 'swindows';
import { ILightBarItem } from 'swindows/src/LightBar';
import { border, title } from './window-options';
import { isBack } from './UI';
type onSelect = (domain: string, zone: string, net: string, node: IFtnNodeList['Node'], nodeListFile: string) => void;
const cgadefs: ICgaDefs = load('cga_defs.js');
const fidoSysCfg: IFidoSysCfg = load('fido_syscfg.js');
const ftnNodeList: IFtnNodeList = load('ftn_nodelist.js');
const { file_exists, format } = js.global;
export default class Search {
windowManager: swindows.WindowManager;
inputWindow: swindows.ControlledWindow;
input: swindows.Input;
resultsWindow: swindows.ControlledWindow;
resultsMenu?: swindows.LightBar;
onSelect: onSelect;
state: 'input' | 'results' = 'input';
constructor(wm: swindows.WindowManager, onSelect: onSelect) {
this.windowManager = wm;
this.onSelect = onSelect;
const iwOptions = {
border,
title,
footer: {
text: 'Hit enter to search',
alignment: swindows.defs.ALIGNMENT.RIGHT,
},
position: { x: 0, y: 0 },
size: { width: wm.size.width, height: 3 },
windowManager: wm,
attr: (cgadefs.BG_BLUE|cgadefs.WHITE) as swindows.types.attr,
name: `Search window`,
}
iwOptions.title.text = 'Search';
this.inputWindow = new swindows.ControlledWindow(iwOptions);
const rwOptions = {
border,
position: { x: 0, y: 3 },
size: { width: wm.size.width, height: wm.size.height - 4 },
windowManager: wm,
attr: (cgadefs.BG_BLACK|cgadefs.LIGHTGRAY) as swindows.types.attr,
scrollBar: { vertical: { enabled: true } },
name: `Search results window`,
}
this.resultsWindow = new swindows.ControlledWindow(rwOptions);
this.input = new swindows.Input({
window: this.inputWindow,
leftExit: true,
});
}
search(str: string): ILightBarItem[] {
const ret: ILightBarItem[] = [];
str = str.toLowerCase();
const w = Math.floor((this.resultsWindow.size.width - 2 /* borders */ - 1 /* scrollbar */ - 1 /* space */ - 15 /* addr w/space */) / 3 /* sysop, name, location */);
const w1 = w - 1;
const fmt = `%-15s%-${w}s%-${w}s%-${w}s`;
const ftnDomains = new fidoSysCfg.FTNDomains();
for (const fn in ftnDomains.nodeListFN) {
if (!file_exists(ftnDomains.nodeListFN[fn] as string)) continue;
const nodeList = new ftnNodeList.NodeList(ftnDomains.nodeListFN[fn] as string, false);
for (const entry of nodeList.entries) {
const s = `${entry.addr} ${entry.sysop} ${entry.name} ${entry.location}`.toLowerCase();
if (s.search(str) < 0) continue;
ret.push({
text: format(fmt, entry.addr, entry.sysop.substring(0, w1), entry.name.substring(0, w1), entry.location.substring(0, w1)),
onSelect: () => this.onSelect(fn, '', '', entry, ftnDomains.nodeListFN[fn] as string),
});
}
}
return ret;
}
getCmd(str: string): string | undefined {
if (this.state === 'input') {
const ret = this.input.getCmd(str);
if (ret === undefined) return;
if (ret === '') return '';
this.resultsWindow.clear();
this.resultsWindow.write(`\x01h\x01wSearching for \x01c${ret}\x01w...`);
this.windowManager.refresh();
const items = this.search(ret);
if (items.length > 0) {
this.state = 'results';
this.resultsMenu = new swindows.LightBar({
items,
name: `Search results LightBar`,
window: this.resultsWindow,
});
this.resultsMenu.draw();
} else {
this.state = 'input';
this.resultsWindow.clear();
this.resultsWindow.write(`\x01h\x01wNo results found for \x01c${ret}\x01w.`);
}
return;
} else if (this.resultsMenu !== undefined) {
if (isBack(str.toLowerCase())) {
this.resultsWindow.clear();
this.state = 'input';
} else {
this.resultsMenu.getCmd(str);
}
}
}
close(): void {
this.inputWindow.close();
this.resultsWindow.close();
}
}
\ No newline at end of file
import type { ICgaDefs, IKeyDefs, IFtnNodeList } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import * as swindows from 'swindows';
import { ISettings } from './settings';
import Help from './Help';
import DomainMenu from './DomainMenu';
import ZoneMenu from './ZoneMenu';
import NetMenu from './NetMenu';
import NodeMenu from './NodeMenu';
import NodeInfo from './NodeInfo';
import Search from './Search';
const cgadefs: ICgaDefs = load('cga_defs.js');
const { strip_ctrl } = js.global;
interface IHandler {
getCmd: (str: string) => string | undefined | void,
close: () => void,
}
const keydefs: IKeyDefs = load('key_defs.js');
const menuPosition: swindows.types.IPosition = { x: 0, y: 0 };
export function isBack(cmd: string): boolean {
return (cmd === keydefs.KEY_LEFT || cmd === 'q' || cmd === 'c' || cmd === '\b' || cmd === '\x1b');
}
export default class UI {
windowManager: swindows.WindowManager;
settings: ISettings;
menuSize: swindows.types.ISize;
helpWindow: Help;
domainMenu: DomainMenu;
zoneMenu?: ZoneMenu;
netMenu?: NetMenu;
nodeMenu?: NodeMenu;
nodeInfo?: NodeInfo;
searchWindow?: Search;
activeComponent: IHandler;
backStack: IHandler[] = [];
constructor(windowManager: swindows.WindowManager, settings: ISettings) {
this.windowManager = windowManager;
this.settings = settings;
this.menuSize = { width: windowManager.size.width, height: windowManager.size.height - 1 };
this.helpWindow = new Help(windowManager, menuPosition, this.menuSize);
this.domainMenu = new DomainMenu(windowManager, menuPosition, this.menuSize, this.selectDomain.bind(this), this.settings);
this.activeComponent = this.domainMenu;
const status = '\x01h\x01wCTRL-Q\x01cuit \x01b\xB3 \x01h\x01wCTRL-S\x01cearch \x01b\xB3 \x01wCTRL-G\x01cet help';
const statusBar = new swindows.Window({ windowManager, position: { x: 0, y: windowManager.size.height - 1 }, size: { width: windowManager.size.width, height: 1 }, attr: ((cgadefs.BG_BLUE|cgadefs.WHITE) as swindows.types.attr) });
statusBar.cursor = { x: statusBar.size.width - strip_ctrl(status).length, y: 0 };
statusBar.write(status);
}
stash() {
this.backStack.push(this.activeComponent);
}
unstash() {
this.activeComponent = this.backStack.pop() ?? this.domainMenu;
}
selectDomain(domain: string, nodeListFile: string) {
this.zoneMenu = new ZoneMenu(this.windowManager, menuPosition, this.menuSize, domain, nodeListFile, this.selectZone.bind(this));
this.stash();
this.activeComponent = this.zoneMenu;
}
selectZone(domain: string, zone: string, nodeListFile: string) {
this.netMenu = new NetMenu(this.windowManager, menuPosition, this.menuSize, domain, zone, nodeListFile, this.selectNet.bind(this));
this.stash();
this.activeComponent = this.netMenu;
}
selectNet(domain: string, zone: string, net: string, nodeListFile: string) {
this.nodeMenu = new NodeMenu(this.windowManager, menuPosition, this.menuSize, domain, zone, net, nodeListFile, this.selectNode.bind(this));
this.stash();
this.activeComponent = this.nodeMenu;
}
selectNode(domain: string, zone: string, net: string, node: IFtnNodeList['Node']) {
this.nodeInfo = new NodeInfo(this.windowManager, { x: 1, y: Math.floor((this.windowManager.size.height - 13) / 2) }, { width: this.windowManager.size.width - 2, height: 13 }, domain, zone, net, node);
this.stash();
this.activeComponent = this.nodeInfo;
}
getCmd(cmd: string): boolean {
const lc = cmd.toLowerCase();
if (cmd === keydefs.CTRL_Q) return false;
if (cmd === keydefs.CTRL_G && !(this.activeComponent instanceof Help)) {
this.stash();
this.helpWindow.window.open();
this.helpWindow.window.raise(true);
this.activeComponent = this.helpWindow;
} else if (cmd === keydefs.CTRL_S && !(this.activeComponent instanceof Search)) {
this.stash();
this.searchWindow = new Search(this.windowManager, this.selectNode.bind(this));
this.activeComponent = this.searchWindow;
} else if (this.activeComponent instanceof Search) {
if (this.activeComponent.getCmd(cmd) !== undefined) {
this.activeComponent.close();
this.unstash();
}
} else {
if (isBack(lc) && this.backStack.length > 0) {
this.activeComponent.close();
this.unstash();
} else {
this.activeComponent.getCmd(lc);
}
}
return true;
}
}
\ No newline at end of file
import type { IFtnNodeList } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import { WindowManager } from "swindows";
import { IPosition, ISize } from "swindows/src/types";
import Menu from './Menu';
import { ILightBarItem } from 'swindows/src/LightBar';
type onSelect = (domain: string, zone: string, nodeListFile: string) => void;
const ftnNodeList: IFtnNodeList = load('ftn_nodelist.js');
export default class ZoneMenu extends Menu {
domain: string;
nodeListFile: string;
onSelect: onSelect;
constructor(wm: WindowManager, position: IPosition, size: ISize, domain: string, nodeListFile: string, onSelect: onSelect) {
super(domain, wm, position, size);
this.domain = domain;
this.nodeListFile = nodeListFile;
this.onSelect = onSelect;
this.addItems();
}
getItems(): ILightBarItem[] {
const items: ILightBarItem[] = [];
const nodeList = new ftnNodeList.NodeList(this.nodeListFile, false);
nodeList.entries.sort((a, b) => {
const az = parseInt(a.addr.split(':')[0], 10);
const bz = parseInt(b.addr.split(':')[0], 10);
return az - bz;
})
const zones: string[] = [];
for (const entry of nodeList.entries) {
const zone = entry.addr.split(':')[0];
if (zones.includes(zone)) continue;
zones.push(zone);
items.push({
text: `Zone ${zone}`,
onSelect: () => this.onSelect(this.domain, zone, this.nodeListFile),
});
}
return items;
}
}
const { file_exists, system, File } = js.global;
const settingsFile = `${system.ctrl_dir}modopts.d/nodelist-browser.ini`;
export interface ISettings {
domains?: Record<string, string>,
nodelists?: Record<string, string>,
}
export function migrate(): void {
if (file_exists(settingsFile)) return;
let f = new File(`${system.ctrl_dir}modopts.ini`);
if (!f.open('r')) return;
const ini = f.iniGetObject('fido_nodelist_browser');
f.close();
if (ini === null) return;
const settings: Record<string, Record<string, string>> = { domains: {}, nodelists: {} };
for (const key in ini) {
if (key.search(/^domain_/) > -1) {
const domain = key.substring(7);
if (domain.length > 0) settings.domains[domain] = ini[key] as string;
} else if (key.search(/^nodelist_/) > -1) {
const domain = key.substring(9);
if (domain.length > 0) settings.nodelists[domain] = ini[key] as string;
}
}
f = new File(settingsFile);
if (!f.open('w+')) return;
f.iniSetObject('nodelist-browser:domains', settings.domains);
f.iniSetObject('nodelist-browser:nodelists', settings.nodelists);
f.close();
}
export function load(): ISettings {
const settings: ISettings = { domains: {}, nodelists: {} };
const f = new File(settingsFile);
if (!f.open('r')) return settings;
const domains = f.iniGetObject('nodelist-browser:domains');
const nodelists = f.iniGetObject('nodelist-browser:nodelists');
f.close();
if (domains !== null) settings.domains = domains as Record<string, string>;
if (nodelists !== null) settings.nodelists = nodelists as Record<string, string>;
js.global.log(7, JSON.stringify(settings));
return settings;
}
\ No newline at end of file
import type { ICgaDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import * as swindows from 'swindows';
const cgadefs: ICgaDefs = load('cga_defs.js');
export const border = {
style: swindows.defs.BORDER_STYLE.SINGLE,
pattern: swindows.defs.BORDER_PATTERN.DIAGONAL,
attr: [
((cgadefs.BG_BLACK|cgadefs.WHITE) as swindows.types.attr),
((cgadefs.BG_BLACK|cgadefs.LIGHTCYAN) as swindows.types.attr),
((cgadefs.BG_BLACK|cgadefs.CYAN) as swindows.types.attr),
((cgadefs.BG_BLACK|cgadefs.LIGHTBLUE) as swindows.types.attr),
],
}
export const title = {
text: '',
attr: ((cgadefs.BG_BLACK|cgadefs.WHITE) as swindows.types.attr),
}
import type { ICgaDefs, ISbbsDefs } from '@swag/ts4s';
import { load } from '@swag/ts4s';
import * as swindows from 'swindows';
import * as settings from './lib/settings';
import UI from './lib/UI';
const cgadefs: ICgaDefs = load('cga_defs.js');
const sbbsdefs: ISbbsDefs = load('sbbsdefs.js');
const { bbs, console } = js.global;
function init() {
js.on_exit(`console.ctrlkey_passthru = ${console.ctrlkey_passthru};`);
js.on_exit(`console.attributes = ${console.attributes};`);
js.on_exit(`bbs.sys_status = ${bbs.sys_status};`);
js.on_exit('console.home();');
js.on_exit('console.write("\x1B[0;37;40m");');
js.on_exit('console.write("\x1B[2J");');
js.on_exit('console.write("\x1B[?25h");');
js.time_limit = 0;
bbs.sys_status |= sbbsdefs.SS_MOFF;
console.ctrlkey_passthru = '+GQS';
console.clear(cgadefs.BG_BLACK|cgadefs.LIGHTGRAY);
settings.migrate();
}
function main(): void {
const s = settings.load();
const windowManager = new swindows.WindowManager();
windowManager.hideCursor();
const ui = new UI(windowManager, s);
while (!js.terminated) {
windowManager.refresh();
const input = console.getkey();
if (!ui.getCmd(input)) break;
}
}
init();
main();
// To-do:
// Migrate fido-nodelist-browser.js settings?
// Modopts settings for colors etc.
\ No newline at end of file
{
"extends": "./node_modules/@swag/ts4s/tsconfig-synchronet.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
"target": "ESNext",
"lib": ["ESNext"]
},
"include": [
"src/nodelist-browser.ts",
],
"exclude": [
"node_modules"
]
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment