history: nginx reverse proxy on Server A, directory restructure plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rokojori 2026-07-12 13:05:53 +02:00
parent c44f14c733
commit f871804e5b
14 changed files with 695 additions and 11 deletions

View File

@ -0,0 +1,145 @@
import { Editor } from '../../editor/Editor.js';
import { ContextMenuDirectory, ContextMenuReadOnlyEntry } from '../context-menu/context-menu.js';
declare const CodeMirror: any;
class CodePanel extends HTMLElement
{
currentPath: string | null = null;
_pinned: boolean = false;
_cm: any = null;
_initialized = false;
_ignoreChange = false;
connectedCallback(): void
{
if ( this._initialized ) return;
this._initialized = true;
this.className = 'code-panel';
this.innerHTML = `
<div class="cp-toolbar">
<button class="cp-pin" title="Pin — keep this file when selecting others">Pin</button>
<button class="cp-undo" title="Undo (Ctrl+Z)" disabled></button>
<button class="cp-redo" title="Redo (Ctrl+Y)" disabled></button>
<button class="cp-save" title="Save (Ctrl+S)" disabled>Save</button>
</div>
<div class="cp-empty">Open a file from the file tree</div>
<div class="cp-editor" style="display:none"></div>
`;
const editorDiv = this.querySelector( '.cp-editor' ) as HTMLElement;
this._cm = CodeMirror( editorDiv,
{
value: '',
lineNumbers: true,
theme: 'cp-dark',
indentWithTabs: false,
tabSize: 2,
indentUnit: 2,
lineWrapping: false,
readOnly: false,
} );
this._cm.on( 'change', () => this._onContentChanged() );
this.querySelector( '.cp-pin' )!.addEventListener( 'click', () => this._togglePin() );
this.querySelector( '.cp-save' )!.addEventListener( 'click', () => this._save() );
this.querySelector( '.cp-undo' )!.addEventListener( 'click', () => this._cm.undo() );
this.querySelector( '.cp-redo' )!.addEventListener( 'click', () => this._cm.redo() );
Editor.get().onDocumentOpened.addListener( ( e ) =>
{
if ( 'code-panel' !== e.editorTag ) return;
if ( ! this._pinned ) this._loadDocument( e.path, e.content );
} );
document.addEventListener( 'keydown', ( e: KeyboardEvent ) =>
{
if ( ! this.currentPath ) return;
if ( e.ctrlKey && e.key === 's' ) { e.preventDefault(); this._save(); }
} );
}
_loadDocument( path: string, content: string ): void
{
this.currentPath = path;
this._updateTabLabel( path );
this.querySelector( '.cp-empty' )!.setAttribute( 'style', 'display:none' );
( this.querySelector( '.cp-editor' ) as HTMLElement ).style.display = '';
this._ignoreChange = true;
this._cm.setValue( content );
this._cm.clearHistory();
this._cm.setOption( 'mode', this._resolveMode( path ) );
this._ignoreChange = false;
this._updateButtons( false );
this._cm.refresh();
}
_resolveMode( filePath: string ): string
{
const name = filePath.slice( filePath.lastIndexOf( '/' ) + 1 );
if ( name.endsWith( '.js' ) || name.endsWith( '.json' ) ) return 'javascript';
if ( name.endsWith( '.ts' ) ) return 'javascript';
if ( name.endsWith( '.css' ) ) return 'css';
if ( name.endsWith( '.html' ) || name.endsWith( '.htm' ) ) return 'htmlmixed';
if ( name.endsWith( '.xml' ) || name.endsWith( '.svg' ) ) return 'xml';
if ( name.endsWith( '.md' ) ) return 'markdown';
return 'null';
}
_onContentChanged(): void
{
if ( this._ignoreChange || ! this.currentPath ) return;
const content = this._cm.getValue();
Editor.get().markDirty( this.currentPath, content );
this._updateButtons( true );
}
_togglePin(): void
{
this._pinned = ! this._pinned;
this.querySelector( '.cp-pin' )!.classList.toggle( 'pinned', this._pinned );
}
async _save(): Promise<void>
{
if ( ! this.currentPath ) return;
await Editor.get().save( this.currentPath );
this._updateButtons( false );
}
_updateTabLabel( path: string ): void
{
const name = path ? path.slice( path.lastIndexOf( '/' ) + 1 ) : '';
this.dispatchEvent( new CustomEvent( 'panel:label-change', { bubbles: true, detail: { label: '📝 ' + name } } ) );
}
_updateButtons( dirty: boolean ): void
{
( this.querySelector( '.cp-save' ) as HTMLButtonElement ).disabled = ! dirty;
( this.querySelector( '.cp-undo' ) as HTMLButtonElement ).disabled = this._cm.historySize().undo < 1;
( this.querySelector( '.cp-redo' ) as HTMLButtonElement ).disabled = this._cm.historySize().redo < 1;
}
addContextMenuEntries( dir: ContextMenuDirectory ): void
{
if ( this.currentPath )
{
dir.add( new ContextMenuReadOnlyEntry( dir, `Editing: ${this.currentPath}` ) );
}
else
{
dir.add( new ContextMenuReadOnlyEntry( dir, 'No file open' ) );
}
}
}
customElements.define( 'code-panel', CodePanel );

View File

@ -92,6 +92,7 @@ class EditorShell extends HTMLElement {
customElements.whenDefined( 'tab-container' ),
customElements.whenDefined( 'file-tree-panel' ),
customElements.whenDefined( 'html-editor-panel' ),
customElements.whenDefined( 'code-panel' ),
this._loadLayout(),
] );

View File

@ -31,6 +31,7 @@ class FileTreePanel extends HTMLElement {
this.querySelector( '[data-action="add-dir"]' )!.addEventListener( 'click', () => this.addDirectory() );
Editor.get().onFilesChanged.addListener( () => this.refresh() );
Editor.get().onFileTypeUnknown.addListener( ( e ) => this._showTypeError( e.path ) );
await this.refresh();
}
@ -369,10 +370,27 @@ class FileTreePanel extends HTMLElement {
${this.renderNodes(n.children ?? [], depth + 1)}
</li>`;
}
const isHtml = n.name.endsWith('.html');
return `<li class="ftp-file${isHtml ? ' ftp-html' : ''}" data-path="${n.path}">${n.name}</li>`;
return `<li class="ftp-file" data-path="${n.path}">${n.name}</li>`;
}).join('') + '</ul>';
}
_showTypeError( filePath: string ): void
{
const lastDot = filePath.lastIndexOf( '.' );
const ext = lastDot === -1 ? '' : filePath.slice( lastDot );
const msg = ext ? `Can't open extension "${ext}"` : `Can't open file without extension`;
const tree = this.querySelector( '.ftp-tree' )!;
const existing = tree.querySelector( '.ftp-type-error' );
if ( existing ) existing.remove();
const div = document.createElement( 'div' );
div.className = 'ftp-type-error';
div.textContent = msg;
tree.prepend( div );
setTimeout( () => div.remove(), 3000 );
}
}
customElements.define('file-tree-panel', FileTreePanel);

View File

@ -40,6 +40,7 @@ class HtmlEditorPanel extends HTMLElement
Editor.get().onDocumentOpened.addListener( ( e ) =>
{
if ( 'html-editor-panel' !== e.editorTag ) return;
if ( ! this._pinned ) this._loadDocument( e.path, e.content );
} );

View File

@ -56,13 +56,13 @@ class TabContainer extends HTMLElement {
Editor.get().onDocumentDirty.addListener( ( e ) =>
{
const tab = this.tabs.find( t => t.panelType === 'html-editor' && ( t.element as any ).currentPath === e.path );
const tab = this.tabs.find( t => ( t.element as any ).currentPath === e.path );
if ( tab ) { tab.dirty = true; this.renderBar(); }
} );
Editor.get().onDocumentSaved.addListener( ( e ) =>
{
const tab = this.tabs.find( t => t.panelType === 'html-editor' && ( t.element as any ).currentPath === e.path );
const tab = this.tabs.find( t => ( t.element as any ).currentPath === e.path );
if ( tab ) { tab.dirty = false; this.renderBar(); }
} );
}
@ -75,6 +75,7 @@ class TabContainer extends HTMLElement {
const addDir = new ContextMenuDirectory(root, 'Add');
const panelTypes = [
{ label: 'HTML Editor', panelType: 'html-editor', tag: 'html-editor-panel' },
{ label: 'Code Editor', panelType: 'code-panel', tag: 'code-panel' },
{ label: 'File Tree', panelType: 'file-tree', tag: 'file-tree-panel' },
{ label: 'Rojo Chat', panelType: 'rojo-chat', tag: 'rojo-chat-panel' },
];

View File

@ -1,10 +1,12 @@
import { EventSlot } from '../library-ts/browser/events/EventSlot.js';
import { FileEditorRegistry } from './FileEditorRegistry.js';
export interface DocumentOpenedEvent
{
path: string;
content: string;
editorTag: string;
}
export interface DocumentPathEvent
@ -30,15 +32,26 @@ export class Editor
projectName: string = '';
openDocs: Map<string, { content: string; dirty: boolean }> = new Map();
activeDoc: string | null = null;
fileEditorRegistry: FileEditorRegistry = new FileEditorRegistry();
readonly onDocumentOpened: EventSlot<DocumentOpenedEvent> = new EventSlot();
readonly onDocumentDirty: EventSlot<DocumentPathEvent> = new EventSlot();
readonly onDocumentSaved: EventSlot<DocumentPathEvent> = new EventSlot();
readonly onFilesChanged: EventSlot<void> = new EventSlot();
readonly onFileTypeUnknown: EventSlot<DocumentPathEvent> = new EventSlot();
async openDocument( filePath: string ): Promise<void>
{
await this.fileEditorRegistry.load( this.projectId );
const editorTag = this.fileEditorRegistry.resolve( filePath );
if ( editorTag === null )
{
this.onFileTypeUnknown.dispatch( { path: filePath } );
return;
}
if ( ! this.openDocs.has( filePath ) )
{
const res = await fetch( `/api/files/${this.projectId}/${filePath}` );
@ -48,7 +61,7 @@ export class Editor
this.activeDoc = filePath;
const entry = this.openDocs.get( filePath )!;
this.onDocumentOpened.dispatch( { path: filePath, content: entry.content } );
this.onDocumentOpened.dispatch( { path: filePath, content: entry.content, editorTag } );
}
markDirty( filePath: string, content: string ): void

View File

@ -0,0 +1,84 @@
interface RegistryEntry
{
suffix: string;
editor: string;
}
export class FileEditorRegistry
{
static readonly DefaultEntries: RegistryEntry[] =
[
{ suffix: 'html', editor: 'HTMLEditorPanel' },
{ suffix: 'htm', editor: 'HTMLEditorPanel' },
{ suffix: 'js', editor: 'CodePanel' },
{ suffix: 'ts', editor: 'CodePanel' },
{ suffix: 'css', editor: 'CodePanel' },
{ suffix: 'json', editor: 'CodePanel' },
{ suffix: 'md', editor: 'CodePanel' },
{ suffix: 'txt', editor: 'CodePanel' },
{ suffix: 'svg', editor: 'CodePanel' },
{ suffix: 'xml', editor: 'CodePanel' },
{ suffix: 'yaml', editor: 'CodePanel' },
{ suffix: 'yml', editor: 'CodePanel' },
{ suffix: 'sh', editor: 'CodePanel' },
{ suffix: 'py', editor: 'CodePanel' },
];
static readonly EditorTagNames: Record<string, string> =
{
'HTMLEditorPanel': 'html-editor-panel',
'CodePanel': 'code-panel',
};
projectEntries: RegistryEntry[] | null = null;
_loaded = false;
async load( projectId: string ): Promise<void>
{
if ( this._loaded ) return;
this._loaded = true;
try
{
const res = await fetch( `/api/files/${projectId}/workspace/editor/file-editors.json` );
if ( ! res.ok ) return;
const data = await res.json();
if ( Array.isArray( data ) ) this.projectEntries = data;
}
catch {}
}
resolve( filePath: string ): string | null
{
const filename = filePath.slice( filePath.lastIndexOf( '/' ) + 1 );
if ( this.projectEntries !== null )
{
const editor = this._matchSuffix( filename, this.projectEntries );
if ( editor !== null ) return this._resolveTag( editor );
}
const editor = this._matchSuffix( filename, FileEditorRegistry.DefaultEntries );
if ( editor !== null ) return this._resolveTag( editor );
return null;
}
_resolveTag( editorName: string ): string
{
return FileEditorRegistry.EditorTagNames[ editorName ] ?? editorName;
}
_matchSuffix( filename: string, entries: RegistryEntry[] ): string | null
{
for ( const entry of entries )
{
if ( filename.endsWith( '.' + entry.suffix ) )
{
return entry.editor;
}
}
return null;
}
}

@ -1 +1 @@
Subproject commit 89534bb72b8d2119ca5e12da05549edbd9133945
Subproject commit f24cc9d8b80ccbdd2a20fa66dc7cf854204d566f

277
src/rojos/roject.svg Normal file
View File

@ -0,0 +1,277 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="3840"
height="2160"
viewBox="0 0 3840 2160"
version="1.1"
id="svg5"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
sodipodi:docname="roject.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#333333"
bordercolor="#404040"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#333333"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="0.20890503"
inkscape:cx="1969.7946"
inkscape:cy="1179.962"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g17" /><defs
id="defs2"><linearGradient
id="linearGradient12"
inkscape:collect="never"><stop
style="stop-color:#00bcf5;stop-opacity:1;"
offset="0"
id="stop12" /><stop
style="stop-color:#296af5;stop-opacity:0.65098041;"
offset="0.3475728"
id="stop14" /><stop
style="stop-color:#7800f5;stop-opacity:0;"
offset="1"
id="stop13" /></linearGradient><linearGradient
id="linearGradient9"
inkscape:collect="never"><stop
style="stop-color:#ff26a7;stop-opacity:1;"
offset="0"
id="stop9" /><stop
style="stop-color:#ff3426;stop-opacity:1;"
offset="1"
id="stop10" /></linearGradient><linearGradient
id="linearGradient7"
inkscape:collect="never"><stop
style="stop-color:#2636ff;stop-opacity:1;"
offset="0"
id="stop7" /><stop
style="stop-color:#29006f;stop-opacity:1;"
offset="1"
id="stop8" /></linearGradient><linearGradient
id="linearGradient5"
inkscape:collect="never"><stop
style="stop-color:#beffc5;stop-opacity:1;"
offset="0"
id="stop5" /><stop
style="stop-color:#26c5ff;stop-opacity:1;"
offset="0.53096259"
id="stop11" /><stop
style="stop-color:#0051eb;stop-opacity:1;"
offset="1"
id="stop6" /></linearGradient><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7940"><rect
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect7942"
width="1440"
height="810"
x="0"
y="0" /></clipPath><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient5"
id="linearGradient6"
x1="2094.8511"
y1="824.97723"
x2="2175.8127"
y2="1376.4214"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.69112949,0,0,0.69112949,529.92586,259.08767)" /><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient7"
id="linearGradient8"
x1="2107.4609"
y1="790.479"
x2="2563.2524"
y2="1203.4646"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.69112949,0,0,0.69112949,529.92586,259.08767)" /><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient9"
id="linearGradient10"
x1="1966.5072"
y1="1228.4695"
x2="1874.9818"
y2="528.3299"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.69112949,0,0,0.69112949,529.92586,259.08767)" /><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter10"
x="-0.32921062"
y="-0.82302656"
width="1.6584212"
height="2.6460531"><feGaussianBlur
inkscape:collect="always"
stdDeviation="329.39922"
id="feGaussianBlur10" /></filter><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter11"
x="-0.017136762"
y="-0.0835418"
width="1.0342735"
height="1.1670836"><feGaussianBlur
inkscape:collect="always"
stdDeviation="11.456677"
id="feGaussianBlur11" /></filter><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter12"
x="-0.1102434"
y="-0.51284629"
width="1.2203709"
height="2.0256926"><feGaussianBlur
inkscape:collect="always"
stdDeviation="61.763442"
id="feGaussianBlur12" /></filter><radialGradient
inkscape:collect="never"
xlink:href="#linearGradient12"
id="radialGradient13"
cx="1008.5775"
cy="617.15338"
fx="1008.5775"
fy="617.15338"
r="749.229"
gradientTransform="matrix(3.747293,0,0,0.21523126,-1392.2413,742.81336)"
gradientUnits="userSpaceOnUse" /><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter16"
x="-0.32921062"
y="-0.82302656"
width="1.6584212"
height="2.6460531"><feGaussianBlur
inkscape:collect="always"
stdDeviation="329.39922"
id="feGaussianBlur16" /></filter></defs><g
inkscape:label="Content"
inkscape:groupmode="layer"
id="layer1"
style="fill:#ffffff;fill-opacity:1"><rect
style="fill:#000000;stroke-linecap:round;stroke-linejoin:round;fill-opacity:1"
id="rect1"
width="3840"
height="2160"
x="0"
y="0"
ry="7.9545546" /><g
id="g17"
transform="translate(38.057367,61.315595)"><ellipse
style="opacity:1;mix-blend-mode:normal;fill:#0a0066;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter10)"
id="path10"
cx="1834.5552"
cy="1143.0068"
rx="1200.6875"
ry="480.27499"
transform="matrix(0.69112949,0,0,0.69112949,566.63996,353.0411)" /><ellipse
style="opacity:1;mix-blend-mode:screen;fill:#005d78;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter16)"
id="ellipse16"
cx="1834.5552"
cy="1143.0068"
rx="1200.6875"
ry="480.27499"
transform="matrix(0.59256734,0,0,0.55929452,795.32629,304.17302)" /><g
id="g12"
style="opacity:1;filter:url(#filter12)"
transform="matrix(0.69112949,0,0,0.69112949,491.63996,291.80532)"><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:457.124px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke-width:11.4281;stroke-linecap:round;stroke-linejoin:round;filter:url(#filter11)"
x="1423.6522"
y="1196.2206"
id="text11"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,-55,0)"><tspan
sodipodi:role="line"
id="tspan11"
x="1423.6522"
y="1196.2206"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:#000000;fill-opacity:1;stroke-width:11.4281">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:457.124px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke-width:11.4281;stroke-linecap:round;stroke-linejoin:round;filter:url(#filter11)"
x="1423.6522"
y="1196.2206"
id="text12"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,-4.2276373,0)"><tspan
sodipodi:role="line"
id="tspan12"
x="1423.6522"
y="1196.2206"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:#000000;fill-opacity:1;stroke-width:11.4281">ROJECT</tspan></text></g><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:315.932px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:url(#linearGradient10);stroke-width:7.8983;stroke-linecap:round;stroke-linejoin:round"
x="1484.9595"
y="1087.6149"
id="text2"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan2"
x="1484.9595"
y="1087.6149"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:url(#linearGradient10);fill-opacity:1;stroke-width:7.8983">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:315.932px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:url(#linearGradient8);stroke-width:7.8983;stroke-linecap:round;stroke-linejoin:round"
x="1498.2201"
y="1087.0574"
id="text1"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan1"
x="1498.2201"
y="1087.0574"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:url(#linearGradient8);fill-opacity:1;stroke-width:7.8983">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:315.932px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:1;fill:url(#linearGradient6);stroke-width:7.8983;stroke-linecap:round;stroke-linejoin:round"
x="1513.8538"
y="1085.8309"
id="text3"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan3"
x="1513.8538"
y="1085.8309"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:url(#linearGradient6);fill-opacity:1;stroke-width:7.8983">ROJECT</tspan></text><g
id="g16"
transform="translate(9.5737285,-66.875187)"><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:67.5143px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;opacity:1;fill:#0056f5;fill-opacity:1;stroke:none;stroke-width:16.8787;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="1480.9904"
y="1349.8354"
id="text15"><tspan
sodipodi:role="line"
id="tspan15"
x="1480.9904"
y="1349.8354"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#0056f5;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:67.5143px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;opacity:1;fill:#00f526;fill-opacity:1;stroke:none;stroke-width:16.8787;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="1480.9904"
y="1342.0404"
id="text16"><tspan
sodipodi:role="line"
id="tspan16"
x="1480.9904"
y="1342.0404"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#00f526;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:67.5143px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;opacity:1;fill:#00cbf5;fill-opacity:1;stroke:none;stroke-width:16.8787;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
x="1480.9904"
y="1344.5404"
id="text14"><tspan
sodipodi:role="line"
id="tspan14"
x="1480.9904"
y="1344.5404"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#00cbf5;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text></g></g></g></svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -35,6 +35,7 @@ var NAV_DATA = {
title: 'History',
path: 'history/index.html',
children: [
{ title: 'Saturday, 11 July 2026', path: 'history/2026/07-July/11-Saturday/index.html' },
{ title: 'Friday, 10 July 2026', path: 'history/2026/07-July/10-Friday/index.html' },
{ title: 'Thursday, 9 July 2026', path: 'history/2026/07-July/09-Thursday/index.html' },
{ title: 'Monday, 6 July 2026', path: 'history/2026/07-July/06-Monday/index.html' },

View File

@ -28,8 +28,8 @@
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="0.41781005"
inkscape:cx="-276.44141"
inkscape:cy="1626.3371"
inkscape:cx="51.458791"
inkscape:cy="1659.8452"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,2 +1,22 @@
[ Code Panel ]
A new panel window will
[ CodePanel: Feature ]
A new panel window will be added, that can read code files.
Currently there's only one file editor for HTML files.
This should be now extended to any editor panes that can work with mainly text files:
HTMLEditorPanel and CodePanel
When a file is opened it should be opened in the CodePanel except it is an html file, where the HTMLEditorPanel would take over.
[ CodePanel: UI]
Like any panel it will have a toolbar similar like the html editor panel: Pin, Undo, Redo, Save
It will than use a coding window that has line numbers and highlighted code.
[ CodePanel: Technical implementation]
It should use the code mirror editor as editing tool. Later, language servers should be able to be added. They could
however sit on a different machine, so it should be able to confige remote language servers.

View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Session Summary — 11 July 2026</title>
<link rel="stylesheet" href="../../../../_assets_/styles.css">
<link rel="stylesheet" href="../../../../_assets_/nav.css">
</head>
<body>
<div class="page">
<header>
<p class="date">Saturday, 11 July 2026</p>
<h1>Roject — Session Summary</h1>
<p class="subtitle">
nginx reverse proxy on Server A — Gitea moved to a local port,
SSL termination handed to nginx, development.rokojori.com restored.
Project directory restructure planned for next session.
</p>
</header>
<section>
<h2>What we built</h2>
<div class="card">
<h3>nginx reverse proxy on Server A</h3>
<p>
Installed nginx on Server A (development.rokojori.com). Gitea was
previously running directly on port 443 with its own TLS. It was moved
to port 4444 on localhost, running plain HTTP. nginx now owns port 443
for the domain and proxies traffic to Gitea internally.
</p>
<p style="margin-top:0.75rem">
The nginx server block for <code>development.rokojori.com</code> includes
the standard reverse-proxy headers (<code>Host</code>, <code>X-Real-IP</code>,
<code>X-Forwarded-For</code>, <code>X-Forwarded-Proto</code>) and uses
the existing Let's Encrypt certificate.
</p>
<p style="margin-top:0.75rem">
Gitea's <code>app.ini</code> was updated: <code>PROTOCOL = http</code>,
<code>HTTP_PORT = 4444</code>, <code>ROOT_URL = https://development.rokojori.com/</code>.
Port 4444 was closed in the IONOS firewall so Gitea is only reachable
through nginx.
</p>
<div class="tags">
<span class="tag">nginx</span>
<span class="tag">reverse proxy</span>
<span class="tag">Server A</span>
<span class="tag">Gitea :4444</span>
<span class="tag">Let's Encrypt</span>
</div>
</div>
</section>
<section>
<h2>Key Decisions</h2>
<div class="decision">
<strong>nginx handles TLS, Gitea runs plain HTTP locally</strong>
<p>
Gitea's built-in TLS was disabled so that nginx becomes the sole
TLS termination point. This is the standard pattern for a reverse
proxy setup: one certificate, one HTTPS endpoint, all internal
communication over plain HTTP on localhost. It also makes it
straightforward to add a second domain (<code>roject.rokojori.com</code>)
to the same nginx instance later.
</p>
</div>
<div class="decision">
<strong>Port 4444 closed at the firewall level</strong>
<p>
Once Gitea dropped its own TLS, port 4444 became an unencrypted
HTTP port. Closing it in the IONOS firewall ensures Gitea is
unreachable directly from the internet and all traffic must pass
through nginx.
</p>
</div>
<div class="decision">
<strong>Directory restructure deferred to next session</strong>
<p>
A full restructure of the project layout was planned: <code>source/</code>
for all frontend + backend + locale source, <code>build/app/</code> for
compiled output, <code>build/data/db/</code> for JSON user data, and
<code>build/data/storage/</code> for project files. The git submodule
(<code>src/library-ts/</code>) was committed and pushed clean before the
session ended, ready for the <code>git mv</code> that the restructure
requires.
</p>
</div>
</section>
<section>
<h2>Structural Changes</h2>
<div class="card">
<p>
Server A infrastructure only — no source files changed this session.<br>
nginx config: <code>/etc/nginx/sites-available/gitea-server</code> (new)<br>
Gitea config: <code>app.ini</code> — PROTOCOL, HTTP_PORT, ROOT_URL updated<br>
IONOS firewall: port 4444 closed
</p>
</div>
</section>
<footer>
Roject &mdash; session log &mdash; 11 July 2026
</footer>
</div>
<script>var NAV_ROOT = '../../../../';</script>
<script src="../../../../_assets_/nav-data.js"></script>
<script src="../../../../_assets_/nav.js"></script>
</body>
</html>

View File

@ -19,6 +19,11 @@
<section>
<h2>2026 — July</h2>
<div class="card">
<h3><a href="2026/07-July/11-Saturday/index.html">Saturday, 11 July 2026</a></h3>
<p>nginx reverse proxy on Server A — Gitea moved to a local HTTP port, TLS termination handed to nginx, development.rokojori.com restored. Project directory restructure planned.</p>
</div>
<div class="card">
<h3><a href="2026/07-July/10-Friday/index.html">Friday, 10 July 2026</a></h3>
<p>CodePanel — CodeMirror 5 code editor panel; FileEditorRegistry routes files to editors by suffix with a project-level JSON override; all file types now clickable in the tree.</p>