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:
parent
c44f14c733
commit
f871804e5b
|
|
@ -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 );
|
||||
|
|
@ -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(),
|
||||
] );
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
} );
|
||||
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 |
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 — session log — 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue