Compare commits
11 Commits
5aa6c7ca10
...
f463970b6b
Author | SHA1 | Date | |
---|---|---|---|
f463970b6b | |||
9e01628ffb | |||
27c4cda01a | |||
7b4529e8f3 | |||
23d9bb24f2 | |||
330817a745 | |||
f647aba153 | |||
a99cbc4cad | |||
ad5d2c9e92 | |||
857f0ded57 | |||
e64a18b18b |
@@ -34,7 +34,7 @@
|
||||
},
|
||||
{
|
||||
"glob":"**/*",
|
||||
"input":"node_modules/ace-builds/src-noconflict",
|
||||
"input":"node_modules/ace-builds/src-min-noconflict",
|
||||
"output":"ace"
|
||||
}
|
||||
],
|
||||
@@ -44,9 +44,11 @@
|
||||
"node_modules/bootstrap/scss/bootstrap.scss",
|
||||
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
|
||||
"src/assets/css/styles.css",
|
||||
"src/assets/css/overrides.css"
|
||||
"src/assets/css/ace-overrides.css"
|
||||
],
|
||||
"scripts":[
|
||||
"src/libs/showdown.min.js"
|
||||
],
|
||||
"optimization": true
|
||||
},
|
||||
@@ -64,7 +66,7 @@
|
||||
"maximumError":"8kB"
|
||||
}
|
||||
],
|
||||
"optimization":false
|
||||
"optimization": true
|
||||
},
|
||||
"development":{
|
||||
"outputHashing": "all",
|
||||
@@ -110,7 +112,6 @@
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts":[
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
21
newton/fs.js
21
newton/fs.js
@@ -100,6 +100,26 @@ const saveFileAs = () => {
|
||||
});
|
||||
}
|
||||
|
||||
const chooseFolder = () => {
|
||||
return dialog.showOpenDialog(
|
||||
{
|
||||
title: "Choose Folder:",
|
||||
defaultPath: HOME_DIR,
|
||||
properties: [
|
||||
'openDirectory'
|
||||
]
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.canceled) {
|
||||
console.debug("Canceled folder selection...");
|
||||
return "";
|
||||
}
|
||||
console.log(response)
|
||||
|
||||
return response.filePaths[0];
|
||||
});
|
||||
}
|
||||
|
||||
const openFiles = (startPath) => {
|
||||
dialog.showOpenDialog(
|
||||
{
|
||||
@@ -171,6 +191,7 @@ const closeFile = (fpath) => {
|
||||
module.exports = {
|
||||
newtonFs: {
|
||||
setWindow: setWindow,
|
||||
chooseFolder: chooseFolder,
|
||||
openFiles: openFiles,
|
||||
saveFile: saveFile,
|
||||
saveFileAs: saveFileAs,
|
||||
|
@@ -72,7 +72,8 @@ const loadHandlers = () => {
|
||||
ipcMain.handle('openFiles', (eve, startPath) => newton.fs.openFiles(startPath));
|
||||
ipcMain.handle('saveFile', (eve, path, content) => newton.fs.saveFile(path, content));
|
||||
ipcMain.handle('closeFile', (eve, path) => newton.fs.closeFile(path));
|
||||
ipcMain.handle('saveFileAs', (eve, content) => newton.fs.saveFileAs(content));
|
||||
ipcMain.handle('saveFileAs', (eve) => newton.fs.saveFileAs());
|
||||
ipcMain.handle('chooseFolder', (eve) => newton.fs.chooseFolder());
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
|
@@ -19,6 +19,7 @@ contextBridge.exposeInMainWorld('fs', {
|
||||
openFiles: (startPath) => ipcRenderer.invoke("openFiles", startPath),
|
||||
saveFile: (path, content) => ipcRenderer.invoke("saveFile", path, content),
|
||||
saveFileAs: () => ipcRenderer.invoke("saveFileAs"),
|
||||
chooseFolder: () => ipcRenderer.invoke("chooseFolder"),
|
||||
closeFile: (path) => ipcRenderer.invoke("closeFile", path),
|
||||
getPathForFile: (file) => webUtils.getPathForFile(file),
|
||||
onLoadFiles: (callback) => ipcRenderer.on('load-files', (_event, paths) => callback(paths)),
|
||||
|
13
package.json
13
package.json
@@ -4,6 +4,8 @@
|
||||
"version": "0.0.1",
|
||||
"author": "ITDominator",
|
||||
"license": "GPL-2.0-only",
|
||||
"homepage": "https://www.itdominator.com",
|
||||
"email": "1itdominator@gmail.com",
|
||||
"main": "newton/main.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -11,6 +13,7 @@
|
||||
"electron-start": "electron . --trace-warnings --start-as=build",
|
||||
"electron-pack": "ng build --base-href ./ && electron-builder --dir",
|
||||
"electron-dist": "ng build --base-href ./ && electron-builder",
|
||||
"electron-dist-linux": "ng build --base-href ./ && electron-builder --linux deb zip AppImage",
|
||||
"electron-dist-all": "ng build --base-href ./ && electron-builder -mwl",
|
||||
"electron-concurrently": "concurrently 'ng serve' 'electron . --trace-warnings --start-as=ng-serve'",
|
||||
"ng-serve": "ng serve",
|
||||
@@ -24,7 +27,10 @@
|
||||
"icon": "./icos/",
|
||||
"files": [
|
||||
"newton/",
|
||||
"build/"
|
||||
"build/",
|
||||
"!node_modules/ace-builds/",
|
||||
"!node_modules/web-streams-polyfill/",
|
||||
"!node_modules/@angular/"
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools"
|
||||
@@ -33,8 +39,9 @@
|
||||
"target": "portable"
|
||||
},
|
||||
"linux": {
|
||||
"target": "AppImage",
|
||||
"category": "Development"
|
||||
"target": "zip",
|
||||
"category": "Development",
|
||||
"maintainer": "ITDominator"
|
||||
}
|
||||
},
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
|
BIN
public/imgs/only-in-selection.png
Normal file
BIN
public/imgs/only-in-selection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
BIN
public/imgs/whole-word.png
Normal file
BIN
public/imgs/whole-word.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
@@ -2,6 +2,8 @@
|
||||
<info-bar></info-bar>
|
||||
<tabs></tabs>
|
||||
<editors></editors>
|
||||
<search-replace></search-replace>
|
||||
<markdown-preview></markdown-preview>
|
||||
|
||||
<files-modal></files-modal>
|
||||
<lsp-manager></lsp-manager>
|
||||
</div>
|
@@ -3,7 +3,9 @@ import { Component } from '@angular/core';
|
||||
import { InfoBarComponent } from './editor/info-bar/info-bar.component';
|
||||
import { TabsComponent } from './editor/tabs/tabs.component';
|
||||
import { EditorsComponent } from './editor/editors.component';
|
||||
import { FilesModalComponent } from "./common/components/modals/files/files-modal.component";
|
||||
import { SearchReplaceComponent } from "./editor/search-replace/search-replace.component";
|
||||
import { MarkdownPreviewComponent } from "./editor/markdown-preview/markdown-preview.component";
|
||||
import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component";
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +15,9 @@ import { FilesModalComponent } from "./common/components/modals/files/files-moda
|
||||
InfoBarComponent,
|
||||
TabsComponent,
|
||||
EditorsComponent,
|
||||
FilesModalComponent
|
||||
SearchReplaceComponent,
|
||||
MarkdownPreviewComponent,
|
||||
LspManagerComponent,
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
|
@@ -1,16 +0,0 @@
|
||||
.modal-column {
|
||||
min-height: 24em;
|
||||
max-height: 24em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: rgba(116, 0, 0, 0.64);
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, 0.64);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background: rgba(256, 0, 0, 0.64);
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
<div #filesModal
|
||||
id="filesModal" class="modal fade" tabindex="-1" role="dialog"
|
||||
>
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Files:</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<div class="row">
|
||||
<div class="col modal-column">
|
||||
|
||||
<div #filesList *ngFor="let file of files" class="row">
|
||||
<div class="col-11 title"
|
||||
title="{{file.path}}"
|
||||
uuid="{{file.uuid}}"
|
||||
path="{{file.path}}"
|
||||
>
|
||||
{{file.title}}
|
||||
</div>
|
||||
<div class="col-1 close-button">
|
||||
X
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<input #filesSearch type="text" placeholder="Search..." />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col modal-column">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -1,85 +0,0 @@
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
|
||||
import { FilesModalService } from "../../../services/editor/modals/files-modal.service";
|
||||
import { TabsService } from '../../../services/editor/tabs/tabs.service';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'files-modal',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
templateUrl: './files-modal.component.html',
|
||||
styleUrl: './files-modal.component.css',
|
||||
host: {
|
||||
'class': ''
|
||||
}
|
||||
})
|
||||
export class FilesModalComponent {
|
||||
private unsubscribe: Subject<void> = new Subject();
|
||||
|
||||
private filesModalService: FilesModalService = inject(FilesModalService);
|
||||
private tabsService: TabsService = inject(TabsService);
|
||||
|
||||
filesModal!: bootstrap.Modal;
|
||||
files: any[] = [];
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.tabsService.getMessage$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((data: ServiceMessage) => {
|
||||
if (data.action === "create-tab") {
|
||||
this.createFileRow(data.fileName, data.fileUUID, data.filePath);
|
||||
}
|
||||
});
|
||||
|
||||
this.filesModalService.showFilesModalRequested$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe(() => {
|
||||
if (!this.filesModal) {
|
||||
this.createModal();
|
||||
}
|
||||
|
||||
this.showModal();
|
||||
});
|
||||
|
||||
this.filesModalService.addFileToModalRequested$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((uuid: string) => {
|
||||
if (!this.filesModal) {
|
||||
this.createModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createModal() {
|
||||
this.filesModal = new bootstrap.Modal("#filesModal", {});
|
||||
}
|
||||
|
||||
public createFileRow(title: string, uuid: string, path: string): void {
|
||||
this.files.push({title: title, uuid: uuid, path: path})
|
||||
}
|
||||
|
||||
public showModal() {
|
||||
this.filesModal?.toggle();
|
||||
}
|
||||
|
||||
}
|
@@ -15,23 +15,23 @@ export const Keybindings: Array<{}> = [
|
||||
name: "openCommandPalette2",
|
||||
bindKey: {linux: "command-shift-/|F1", win: "ctrl-shift-/|F1"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showFilesModal",
|
||||
bindKey: {win: "ctrl-shift-b", mac: "ctrl-shift-b"},
|
||||
service: "filesModalService",
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showFilesList",
|
||||
bindKey: {win: "ctrl-b", mac: "ctrl-b"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showLSPModal",
|
||||
name: "lspManagerPopup",
|
||||
bindKey: {win: "ctrl-shift-l", mac: "ctrl-shift-l"},
|
||||
service: "",
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "markdownPreviewPopup",
|
||||
bindKey: {win: "ctrl-shift-m", mac: "ctrl-shift-m"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "searchPopup",
|
||||
bindKey: {win: "ctrl-f", mac: "ctrl-f"},
|
||||
readOnly: true
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "replacePopup",
|
||||
bindKey: {win: "ctrl-r", mac: "ctrl-r"},
|
||||
|
@@ -7,6 +7,7 @@ import { ServiceMessage } from '../../types/service-message.type';
|
||||
import { EditorSettings } from "../../configs/editor.config";
|
||||
|
||||
import { NewtonFile } from '../../types/file.type';
|
||||
import { EditorType } from '../../types/editor.type';
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +39,7 @@ export class EditorsService {
|
||||
}
|
||||
|
||||
public set(uuid: string, component: CodeViewComponent) {
|
||||
if (component.isMiniMap) {
|
||||
if (component.mode == EditorType.MiniMap) {
|
||||
this.miniMapView = component;
|
||||
return;
|
||||
}
|
||||
|
@@ -1,26 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { AceLanguageClient, LanguageClientConfig } from 'ace-linters/build/ace-language-client';
|
||||
import { LanguageProvider } from "ace-linters";
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LSPService {
|
||||
export class LspManagerService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
|
||||
lspConfigData!: {};
|
||||
languageProviders: {} = {};
|
||||
languageProviders: {} = {};
|
||||
workspaceFolder: string = "";
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadLSPService();
|
||||
}
|
||||
|
||||
|
||||
private loadLSPService() {
|
||||
this.getLspConfigData().then((lspConfigData: string) => {
|
||||
public loadLspConfigData(): Promise<string | void> {
|
||||
return this.getLspConfigData().then((lspConfigData: string) => {
|
||||
this.lspConfigData = JSON.parse(lspConfigData);
|
||||
|
||||
if (this.lspConfigData["message"]) {
|
||||
@@ -31,11 +35,13 @@ export class LSPService {
|
||||
|
||||
this.lspConfigData = {};
|
||||
}
|
||||
|
||||
return lspConfigData;
|
||||
});
|
||||
}
|
||||
|
||||
public registerEditor(editor: any): void {
|
||||
let modeParts = editor.getSession()["$modeId"].split("/");
|
||||
let modeParts = editor.session.getMode()["$Id"].split("/");
|
||||
let mode = modeParts[ modeParts.length - 1 ];
|
||||
|
||||
if ( !this.languageProviders[mode] ) {
|
||||
@@ -75,14 +81,26 @@ export class LSPService {
|
||||
return LanguageProvider.create(worker);
|
||||
}
|
||||
|
||||
protected setSessionFilePath(session: any, mode: string = "", filePath: string = "") => {
|
||||
if ( !session || !mode || !filePath || !this.languageProviders[mode] ) return;
|
||||
protected setSessionFilePath(session: any, filePath: string = "") {
|
||||
if ( !session || !filePath ) return;
|
||||
let mode = session.getMode()["$Id"];
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
this.languageProviders[mode].setSessionFilePath(session, filePath);
|
||||
}
|
||||
|
||||
protected closeDocument(session: any, mode: string) => {
|
||||
if ( !session || !mode || !this.languageProviders[mode] ) return;
|
||||
protected closeDocument(session: any) {
|
||||
if ( !session ) return;
|
||||
let mode = session.getMode()["$Id"];
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
this.languageProviders[mode].closeDocument(session);
|
||||
}
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MarkdownPreviewService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SearchReplaceService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
@@ -4,10 +4,10 @@ import { ReplaySubject, Observable } from 'rxjs';
|
||||
import { EditSession, UndoManager } from 'ace-builds';
|
||||
import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist';
|
||||
|
||||
import { TabsService } from './tabs/tabs.service';
|
||||
import { TabsService } from './editor/tabs/tabs.service';
|
||||
|
||||
import { NewtonFile } from '../../types/file.type';
|
||||
import { ServiceMessage } from '../../types/service-message.type';
|
||||
import { NewtonFile } from '../types/file.type';
|
||||
import { ServiceMessage } from '../types/service-message.type';
|
||||
|
||||
|
||||
|
5
src/app/common/types/editor.type.ts
Normal file
5
src/app/common/types/editor.type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export abstract class EditorType {
|
||||
static MiniMap: string = "mini-map";
|
||||
static ReadOnly: string = "read-only";
|
||||
static Standalone: string = "standalone";
|
||||
}
|
@@ -5,7 +5,10 @@ import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.s
|
||||
import { FilesModalService } from '../../common/services/editor/modals/files-modal.service';
|
||||
import { TabsService } from '../../common/services/editor/tabs/tabs.service';
|
||||
import { EditorsService } from '../../common/services/editor/editors.service';
|
||||
import { FilesService } from '../../common/services/editor/files.service';
|
||||
import { FilesService } from '../../common/services/files.service';
|
||||
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
||||
import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service';
|
||||
import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
|
||||
|
||||
import { EditorSettings } from "../../common/configs/editor.config";
|
||||
import { NewtonFile } from '../../common/types/file.type';
|
||||
@@ -16,17 +19,20 @@ import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
@Directive()
|
||||
export class CodeViewBase {
|
||||
public uuid: string = uuid.v4();
|
||||
@Input() public isDefault: boolean = false;
|
||||
@Input() public isMiniMap: boolean = false;
|
||||
public uuid: string = uuid.v4();
|
||||
@Input() public isDefault: boolean = false;
|
||||
@Input() public mode: string = "";
|
||||
public leftSiblingUUID!: string;
|
||||
public rightSiblingUUID!: string;
|
||||
|
||||
protected infoBarService: InfoBarService = inject(InfoBarService);
|
||||
protected filesModalService: FilesModalService = inject(FilesModalService);
|
||||
protected tabsService: TabsService = inject(TabsService);
|
||||
protected editorsService: EditorsService = inject(EditorsService);
|
||||
protected filesService: FilesService = inject(FilesService);
|
||||
protected infoBarService: InfoBarService = inject(InfoBarService);
|
||||
protected filesModalService: FilesModalService = inject(FilesModalService);
|
||||
protected tabsService: TabsService = inject(TabsService);
|
||||
protected editorsService: EditorsService = inject(EditorsService);
|
||||
protected filesService: FilesService = inject(FilesService);
|
||||
protected searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
||||
protected markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
||||
protected lspManagerService: LspManagerService = inject(LspManagerService);
|
||||
|
||||
@ViewChild('editor') editorElm!: ElementRef;
|
||||
@Input() editorSettings!: typeof EditorSettings;
|
||||
@@ -93,12 +99,32 @@ export class CodeViewBase {
|
||||
this.editor.showKeyboardShortcuts();
|
||||
}
|
||||
|
||||
public lspManagerPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-lsp-manager";
|
||||
this.lspManagerService.sendMessage(message);
|
||||
}
|
||||
|
||||
public markdownPreviewPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-markdown-preview";
|
||||
this.markdownPreviewService.sendMessage(message);
|
||||
}
|
||||
|
||||
public searchPopup() {
|
||||
this.editor.execCommand("find");
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-search-replace";
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
// this.editor.execCommand("find");
|
||||
}
|
||||
|
||||
public replacePopup() {
|
||||
this.editor.execCommand("replace");
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-search-replace";
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
// this.editor.execCommand("replace");
|
||||
}
|
||||
|
||||
public showFilesList() {
|
||||
@@ -159,6 +185,11 @@ export class CodeViewBase {
|
||||
window.main.toggleFullScreen();
|
||||
}
|
||||
|
||||
public setAsReadOnly() {
|
||||
this.editor.setReadOnly(true);
|
||||
this.editor.setShowPrintMargin(false);
|
||||
}
|
||||
|
||||
public setAsMiniMapView() {
|
||||
this.editor.renderer.showLineNumbers = false;
|
||||
this.editor.renderer.setShowGutter(false);
|
||||
|
@@ -1,21 +1,22 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
// Import Ace and its modes/themes so that `ace` global is defined
|
||||
import * as ace from "ace-builds/src-noconflict/ace";
|
||||
import "ace-builds/src-noconflict/ext-settings_menu";
|
||||
import "ace-builds/src-noconflict/ext-keybinding_menu";
|
||||
import "ace-builds/src-noconflict/ext-command_bar";
|
||||
import "ace-builds/src-noconflict/ext-prompt";
|
||||
import "ace-builds/src-noconflict/ext-code_lens";
|
||||
import "ace-builds/src-noconflict/ext-searchbox";
|
||||
import "ace-builds/src-noconflict/ext-language_tools";
|
||||
//import "ace-builds/src-noconflict/theme-one_dark";
|
||||
//import "ace-builds/src-noconflict/theme-penguins_in_space";
|
||||
import "ace-builds/src-noconflict/theme-gruvbox";
|
||||
import * as ace from "ace-builds/src-min-noconflict/ace";
|
||||
import "ace-builds/src-min-noconflict/ext-settings_menu";
|
||||
import "ace-builds/src-min-noconflict/ext-keybinding_menu";
|
||||
import "ace-builds/src-min-noconflict/ext-command_bar";
|
||||
import "ace-builds/src-min-noconflict/ext-prompt";
|
||||
import "ace-builds/src-min-noconflict/ext-code_lens";
|
||||
// import "ace-builds/src-min-noconflict/ext-searchbox";
|
||||
import "ace-builds/src-min-noconflict/ext-language_tools";
|
||||
// import "ace-builds/src-min-noconflict/theme-one_dark";
|
||||
// import "ace-builds/src-min-noconflict/theme-penguins_in_space";
|
||||
import "ace-builds/src-min-noconflict/theme-gruvbox";
|
||||
|
||||
import { CodeViewBase } from './view.base';
|
||||
|
||||
import { NewtonFile } from '../../common/types/file.type';
|
||||
import { EditorType } from '../../common/types/editor.type';
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
@@ -47,19 +48,7 @@ export class CodeViewComponent extends CodeViewBase {
|
||||
|
||||
private loadAce(): void {
|
||||
this.configAceAndBindToElement()
|
||||
|
||||
if (this.isDefault) {
|
||||
this.editorsService.setActiveEditor(this.uuid);
|
||||
this.addActiveStyling();
|
||||
}
|
||||
|
||||
if (this.isMiniMap) {
|
||||
this.setAsMiniMapView();
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadAceKeyBindings();
|
||||
this.loadAceEventBindings();
|
||||
this.setupRequestedMode();
|
||||
}
|
||||
|
||||
private configAceAndBindToElement(): void {
|
||||
@@ -69,10 +58,34 @@ export class CodeViewComponent extends CodeViewBase {
|
||||
|
||||
this.editor = ace.edit( this.editorElm.nativeElement );
|
||||
this.editor.setOptions( this.editorSettings.CONFIG );
|
||||
|
||||
this.editorsService.set(this.uuid, this);
|
||||
if (this.isDefault) {
|
||||
this.editorsService.setActiveEditor(this.uuid);
|
||||
this.addActiveStyling();
|
||||
this.editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private loadAceKeyBindings(): void {
|
||||
private setupRequestedMode() {
|
||||
switch(this.mode) {
|
||||
case EditorType.Standalone:
|
||||
// Note: Ace editor without any additional Newton setup...
|
||||
break;
|
||||
case EditorType.MiniMap:
|
||||
this.setAsMiniMapView();
|
||||
break;
|
||||
case EditorType.ReadOnly:
|
||||
this.setAsReadOnly();
|
||||
break;
|
||||
default:
|
||||
this.loadNewtonKeyBindings();
|
||||
this.loadNewtonEventBindings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private loadNewtonKeyBindings(): void {
|
||||
let keyBindings = [];
|
||||
for (let i = 0; i < this.editorSettings.KEYBINDINGS.length; i++) {
|
||||
let keyBinding = this.editorSettings.KEYBINDINGS[i];
|
||||
@@ -104,15 +117,23 @@ export class CodeViewComponent extends CodeViewBase {
|
||||
this.editor.commands.addCommands( keyBindings );
|
||||
}
|
||||
|
||||
private loadAceEventBindings(): void {
|
||||
private loadNewtonEventBindings(): void {
|
||||
|
||||
// Note: https://ajaxorg.github.io/ace-api-docs/interfaces/ace.Ace.EditorEvents.html
|
||||
this.editor.on("focus", (e) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
message.rawData = this.editor;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
this.lspManagerService.sendMessage(message);
|
||||
|
||||
message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.rawData = this;
|
||||
this.markdownPreviewService.sendMessage(message);
|
||||
|
||||
this.updateInfoBar();
|
||||
});
|
||||
@@ -154,6 +175,12 @@ export class CodeViewComponent extends CodeViewBase {
|
||||
});
|
||||
|
||||
this.editor.on("changeSession", (session) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.rawData = this.editor;
|
||||
|
||||
this.lspManagerService.sendMessage(message);
|
||||
|
||||
this.updateInfoBar();
|
||||
});
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<code-view [isDefault]="true"></code-view>
|
||||
<hr class="col vr-pane-handle" pane-handle />
|
||||
<code-view></code-view>
|
||||
<code-view [isMiniMap]="true"></code-view>
|
||||
<code-view [mode]="'mini-map'"></code-view>
|
||||
</div>
|
||||
|
||||
<div>
|
@@ -3,7 +3,7 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import { EditorsService } from '../common/services/editor/editors.service';
|
||||
import { TabsService } from '../common/services/editor/tabs/tabs.service';
|
||||
import { FilesService } from '../common/services/editor/files.service';
|
||||
import { FilesService } from '../common/services/files.service';
|
||||
|
||||
import { CodeViewComponent } from "./code-view/view.component";
|
||||
|
||||
|
11
src/app/editor/lsp-manager/lsp-manager.component.css
Normal file
11
src/app/editor/lsp-manager/lsp-manager.component.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.lsp-config-text {
|
||||
display: grid;
|
||||
min-height: 25em;
|
||||
}
|
||||
|
||||
.clear-left-padding {
|
||||
padding-left: 0px;
|
||||
}
|
||||
.clear-right-padding {
|
||||
padding-right: 0px;
|
||||
}
|
54
src/app/editor/lsp-manager/lsp-manager.component.html
Normal file
54
src/app/editor/lsp-manager/lsp-manager.component.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row mt-2 mb-3">
|
||||
|
||||
<div class="col clear-right-padding">
|
||||
<div class="input-group-sm">
|
||||
<label class="form-control" [innerText]="lspManagerService.workspaceFolder || 'Project Path...'">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-auto clear-left-padding">
|
||||
<button class="btn btn-sm btn-dark" (click)="clearWorkspaceFolder()">x</button>
|
||||
</div>
|
||||
|
||||
<div class="col col-auto">
|
||||
<div class="input-group-sm">
|
||||
<button class="btn btn-sm btn-dark" (click)="setWorkspaceFolder()">Workspace Folder</button>
|
||||
<button class="btn btn-sm btn-danger ms-5" (click)="hideLspManager()">X</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mt-2 md-2">
|
||||
<div class="col">
|
||||
LSP Configs:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<code-view #lspEditorComponent [mode]="'standalone'" class="lsp-config-text"></code-view>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2 md-2">
|
||||
<div class="col">
|
||||
Target Editor: <label [innerText]="editor?.id || 'Editor...'"></label>
|
||||
</div>
|
||||
<div class="col">
|
||||
Active Editor Session:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<code-view #sessionEditorComponent [mode]="'read-only'" class="lsp-config-text"></code-view>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
117
src/app/editor/lsp-manager/lsp-manager.component.ts
Normal file
117
src/app/editor/lsp-manager/lsp-manager.component.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { Component, ChangeDetectorRef, ElementRef, HostBinding, ViewChild, inject } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
|
||||
|
||||
import { CodeViewComponent } from '../code-view/view.component';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'lsp-manager',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CodeViewComponent
|
||||
],
|
||||
templateUrl: './lsp-manager.component.html',
|
||||
styleUrl: './lsp-manager.component.css',
|
||||
host: {
|
||||
'class': 'lsp-manager',
|
||||
"(keyup)": "globalLspManagerKeyHandler($event)"
|
||||
}
|
||||
})
|
||||
export class LspManagerComponent {
|
||||
private unsubscribe: Subject<void> = new Subject();
|
||||
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
|
||||
|
||||
lspManagerService: LspManagerService = inject(LspManagerService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
@ViewChild('lspEditorComponent') lspEditorComponent!: CodeViewComponent;
|
||||
@ViewChild('sessionEditorComponent') sessionEditorComponent!: CodeViewComponent;
|
||||
lspTextEditor: any;
|
||||
innerEditor: any;
|
||||
editor: any;
|
||||
|
||||
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.lspTextEditor = this.lspEditorComponent.editor;
|
||||
this.innerEditor = this.sessionEditorComponent.editor;
|
||||
|
||||
this.lspManagerService.loadLspConfigData().then((lspConfigData) => {
|
||||
this.lspTextEditor.session.setMode("ace/mode/json");
|
||||
this.lspTextEditor.session.setValue(lspConfigData);
|
||||
});
|
||||
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private ngOnDestroy() {
|
||||
this.unsubscribe.next();
|
||||
this.unsubscribe.complete();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.lspManagerService.getMessage$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
if (message.action === "toggle-lsp-manager") {
|
||||
this.toggleLspManager(message);
|
||||
} else if (message.action === "set-active-editor") {
|
||||
this.setActiveEditor(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public clearWorkspaceFolder() {
|
||||
this.lspManagerService.workspaceFolder = "";
|
||||
}
|
||||
|
||||
public setWorkspaceFolder() {
|
||||
window.fs.chooseFolder().then((folder: string) => {
|
||||
if (!folder) return;
|
||||
|
||||
this.lspManagerService.workspaceFolder = folder;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public globalLspManagerKeyHandler(event: any) {
|
||||
if (event.ctrlKey && event.shiftKey && event.key === "l") {
|
||||
this.hideLspManager();
|
||||
}
|
||||
}
|
||||
|
||||
public hideLspManager() {
|
||||
this.isHidden = true;
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
private toggleLspManager(message: ServiceMessage) {
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
// Note: hack for issue with setActiveEditor TODO
|
||||
setTimeout(() => {
|
||||
this.innerEditor.setSession(this.editor.session);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
this.editor = message.rawData;
|
||||
// TODO: figure out why this doesn't update the session consistently...
|
||||
// It seems maybe bound to visible state as change detector ref didn't help either.
|
||||
// this.innerEditor.setSession(this.editor.session);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div [innerHtml]="bodyHtml || defaultHtml">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,96 @@
|
||||
import { Component, HostBinding, inject } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'markdown-preview',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './markdown-preview.component.html',
|
||||
styleUrl: './markdown-preview.component.css',
|
||||
host: {
|
||||
'class': 'container-fluid markdown-preview'
|
||||
}
|
||||
})
|
||||
export class MarkdownPreviewComponent {
|
||||
private unsubscribe: Subject<void> = new Subject();
|
||||
|
||||
private markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
converter: any = new showdown.Converter();
|
||||
defaultHtml: string = "<h1>NOT a Markdown file...</h1>"
|
||||
bodyHtml: string = "";
|
||||
|
||||
private editorComponent!: any;
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private ngOnDestroy() {
|
||||
this.unsubscribe.next();
|
||||
this.unsubscribe.complete();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.markdownPreviewService.getMessage$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
if (message.action === "toggle-markdown-preview") {
|
||||
this.toggleMarkdownPreview(message);
|
||||
} else if (message.action === "set-active-editor") {
|
||||
this.setActiveEditor(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private toggleMarkdownPreview(message: ServiceMessage) {
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
setTimeout(() => {
|
||||
this.updatePreview();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
if (this.editorComponent == message.rawData) return;
|
||||
|
||||
this.editorComponent = message.rawData;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
public updatePreview() {
|
||||
let fileMode = this.editorComponent.editor.session.getMode()["$id"];
|
||||
let isMdFile = (fileMode.includes("markdown"));
|
||||
this.bodyHtml = "";
|
||||
|
||||
if (!isMdFile) return;
|
||||
|
||||
let mdStr = this.editorComponent.editor.session.getValue();
|
||||
let pathParts = this.editorComponent.activeFile.path.split("/");
|
||||
let basePath = "file://" + pathParts.slice(0, -1).join("/");
|
||||
this.bodyHtml = this.converter.makeHtml(
|
||||
mdStr.replaceAll("](images", `](${basePath}/images`)
|
||||
.replaceAll("](imgs", `](${basePath}/imgs`)
|
||||
.replaceAll("](pictures", `](${basePath}/pictures`)
|
||||
.replaceAll("](pics", `](${basePath}/pics`)
|
||||
.replaceAll("](screenshots", `](${basePath}/screenshots`)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
39
src/app/editor/search-replace/search-replace.component.css
Normal file
39
src/app/editor/search-replace/search-replace.component.css
Normal file
@@ -0,0 +1,39 @@
|
||||
.buttons > button {
|
||||
min-width: 3.5em;
|
||||
min-height: 2.5em;
|
||||
}
|
||||
|
||||
.width-8em {
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.margin-tb-1em {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: rgba(125, 125, 125, 1);
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.searching,
|
||||
.search-success,
|
||||
.search-fail {
|
||||
border-style: solid;
|
||||
color: rgba(125, 125, 125, 1) !important;
|
||||
}
|
||||
|
||||
.searching {
|
||||
border-color: rgba(0, 225, 225, 0.64) !important;
|
||||
}
|
||||
|
||||
.search-success {
|
||||
background: rgba(136, 204, 39, 0.12) !important;
|
||||
border-color: rgba(136, 204, 39, 1) !important;
|
||||
}
|
||||
|
||||
.search-fail {
|
||||
background: rgba(170, 18, 18, 0.12) !important;
|
||||
border-color: rgba(200, 18, 18, 1) !important;
|
||||
}
|
84
src/app/editor/search-replace/search-replace.component.html
Normal file
84
src/app/editor/search-replace/search-replace.component.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col col-3">
|
||||
<label id="find-status-lbl">Find in Current File</label>
|
||||
</div>
|
||||
|
||||
<div class="col col-4">
|
||||
<label id="find-options-lbl">Finding with Options: {{findOptions || "Case Insensitive"}}</label>
|
||||
</div>
|
||||
|
||||
<div class="col col-5 line-height-32px buttons">
|
||||
<button title="Close Panel"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="hideSearchReplace()">X
|
||||
</button>
|
||||
<button id="whole-word-btn" title="Whole Word"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleWholeWordSearch($event)">
|
||||
<img src="resources/imgs/whole-word.png" />
|
||||
</button>
|
||||
<button id="only-in-selection-btn" title="Only In Selection"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleSelectionOnlyScan($event)">
|
||||
<img src="resources/imgs/only-in-selection.png" />
|
||||
</button>
|
||||
<button id="match-case-btn" title="Match Case"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleCaseSensitive($event)">Aa
|
||||
</button>
|
||||
<button id="use-regex-btn" title="Use Regex"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleRegex($event)">.*
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="margin-tb-1em"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group-sm mb-3">
|
||||
<input #findEntryElm
|
||||
id="find-entry"
|
||||
class="form-control"
|
||||
type="search"
|
||||
(keyup)="searchForString()"
|
||||
placeholder="Find in current file..."
|
||||
aria-label="Find in current file..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-auto">
|
||||
<button id="find-btn" class="width-8em btn btn-sm btn-dark" (click)="findNextEntry()">Find</button>
|
||||
<button id="find-all-btn" class="width-8em btn btn-sm btn-dark" (click)="findAllEntries()">Find All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group-sm mb-3">
|
||||
<input #replaceEntryElm
|
||||
id="replace-entry"
|
||||
class="form-control"
|
||||
type="search"
|
||||
(keyup)="replaceEntry($event)"
|
||||
title="Replace in current file..."
|
||||
placeholder="Replace in current file..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-auto">
|
||||
<button id="replace-btn" class="width-8em btn btn-sm btn-dark" (click)="replaceEntry($event)">Replace</button>
|
||||
<button id="replace-all-btn" class="width-8em btn btn-sm btn-dark" (click)="replaceAll()">Replace All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
268
src/app/editor/search-replace/search-replace.component.ts
Normal file
268
src/app/editor/search-replace/search-replace.component.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import { Component, ElementRef, HostBinding, Input, ViewChild, inject } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'search-replace',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './search-replace.component.html',
|
||||
styleUrl: './search-replace.component.css',
|
||||
host: {
|
||||
'class': 'row search-replace',
|
||||
"(keyup)": "globalSearchReplaceKeyHandler($event)"
|
||||
}
|
||||
})
|
||||
export class SearchReplaceComponent {
|
||||
private unsubscribe: Subject<void> = new Subject();
|
||||
|
||||
private searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
@ViewChild('findEntryElm') findEntryElm!: ElementRef;
|
||||
@ViewChild('replaceEntryElm') replaceEntryElm!: ElementRef;
|
||||
|
||||
private editor!: any;
|
||||
|
||||
@Input() findOptions: string = "";
|
||||
private useWholeWordSearch: boolean = false;
|
||||
private searchOnlyInSelection: boolean = false;
|
||||
private useCaseSensitive: boolean = false;
|
||||
private useRegex: boolean = false;
|
||||
private selection: string = "";
|
||||
private query: string = "";
|
||||
private toStr: string = "";
|
||||
private isBackwards: boolean = false;
|
||||
private isWrap: boolean = true;
|
||||
private searchTimeoutId: number = -1;
|
||||
private searchTimeout: number = 400;
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private ngOnDestroy() {
|
||||
this.unsubscribe.next();
|
||||
this.unsubscribe.complete();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.searchReplaceService.getMessage$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
if (message.action === "toggle-search-replace") {
|
||||
this.toggleSearchReplace(message);
|
||||
} else if (message.action === "set-active-editor") {
|
||||
this.setActiveEditor(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private toggleSearchReplace(message: ServiceMessage) {
|
||||
this.selection = this.editor.getSelectedText();
|
||||
this.findEntryElm.nativeElement.value = this.selection;
|
||||
|
||||
if (this.selection && !this.isHidden) {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
if (this.isHidden) {
|
||||
this.editor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
if (this.editor == message.rawData) return;
|
||||
|
||||
this.editor = message.rawData;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
|
||||
this.searchForString();
|
||||
}
|
||||
|
||||
public hideSearchReplace() {
|
||||
if (this.selection) {
|
||||
this.selection = "";
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHidden = true;
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
public globalSearchReplaceKeyHandler(event: any) {
|
||||
if (event.ctrlKey && event.key === "f") {
|
||||
this.hideSearchReplace();
|
||||
} else if (event.ctrlKey && event.key === "l") {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
} else if (event.ctrlKey && event.key === "r") {
|
||||
this.replaceEntryElm.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public toggleWholeWordSearch(event: any) {
|
||||
let target = event.target;
|
||||
if (target.nodeName === "IMG")
|
||||
target = target.parentElement;
|
||||
|
||||
this.useWholeWordSearch = !this.useWholeWordSearch;
|
||||
target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleSelectionOnlyScan(event: any) {
|
||||
let target = event.target;
|
||||
if (target.nodeName === "IMG")
|
||||
target = target.parentElement;
|
||||
|
||||
this.searchOnlyInSelection = !this.searchOnlyInSelection;
|
||||
target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleCaseSensitive(event: any) {
|
||||
this.useCaseSensitive = !this.useCaseSensitive;
|
||||
event.target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleRegex(event: any) {
|
||||
this.useRegex = !this.useRegex;
|
||||
event.target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
private setFindOptionsLbl() {
|
||||
let findOptionsStr = "";
|
||||
|
||||
if (this.useRegex)
|
||||
findOptionsStr += "Regex"
|
||||
|
||||
findOptionsStr += (findOptionsStr) ? ", " : "";
|
||||
findOptionsStr += (this.useCaseSensitive) ? "Case Sensitive" : "Case InSensitive";
|
||||
|
||||
if (this.searchOnlyInSelection)
|
||||
findOptionsStr += ", Within Current Selection"
|
||||
|
||||
if (this.useWholeWordSearch)
|
||||
findOptionsStr += ", Whole Word"
|
||||
|
||||
this.findOptions = findOptionsStr;
|
||||
}
|
||||
|
||||
|
||||
public findNextEntry() {
|
||||
this.editor.findNext();
|
||||
}
|
||||
|
||||
public findAllEntries() {
|
||||
this.query = this.findEntryElm.nativeElement.value;
|
||||
|
||||
if (!this.query) return;
|
||||
|
||||
let totalCount = this.editor.findAll(this.query, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
}
|
||||
|
||||
public findPreviousEntry() {
|
||||
this.editor.findPrevious();
|
||||
}
|
||||
|
||||
public replaceEntry(event: any) {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
if (event.key !== "Enter") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let fromStr = this.findEntryElm.nativeElement.value;
|
||||
let toStr = this.replaceEntryElm.nativeElement.value;
|
||||
|
||||
if (!fromStr) return;
|
||||
|
||||
let totalCount = this.editor.replace(toStr, fromStr, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
|
||||
this.editor.clearSelection();
|
||||
this.editor.findNext();
|
||||
}
|
||||
|
||||
public replaceAll() {
|
||||
let fromStr = this.findEntryElm.nativeElement.value;
|
||||
let toStr = this.replaceEntryElm.nativeElement.value;
|
||||
|
||||
if (!fromStr) return;
|
||||
|
||||
let totalCount = this.editor.replaceAll(toStr, fromStr, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
}
|
||||
|
||||
public searchForString() {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
if (event.key !== "Enter") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.query = this.findEntryElm.nativeElement.value;
|
||||
|
||||
if (!this.query) return;
|
||||
|
||||
if (this.searchTimeoutId) { clearTimeout(this.searchTimeoutId); }
|
||||
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
let totalCount = this.editor.find(this.query, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
}, this.searchTimeout);
|
||||
}
|
||||
|
||||
}
|
@@ -3,7 +3,7 @@
|
||||
|
||||
/* IDs */
|
||||
#ace_settingsmenu, #kbshortcutmenu {
|
||||
background-color: rgba(0, 0, 0, 0.0);
|
||||
background-color: rgba(64, 64, 64, 0.84);
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
box-shadow: -1px 4px 5px rgba(124, 124, 124, 0.64);
|
||||
padding: 1em 0.5em 2em 1em;
|
||||
@@ -38,6 +38,14 @@
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
|
||||
.ace_optionsMenuEntry button {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.ace_optionsMenuEntry button[ace_selected_button=true] {
|
||||
background: #00e877;
|
||||
}
|
||||
|
||||
.ace_print-margin {
|
||||
width: 1px !important;
|
||||
background-color: rgba(255, 0, 0, 0.84) !important;
|
||||
@@ -52,4 +60,4 @@
|
||||
.ace_sb-v,
|
||||
.ace_sb-h {
|
||||
width: 0.8em !important;
|
||||
}
|
||||
}
|
||||
|
17
src/assets/css/overrides.css
Normal file
17
src/assets/css/overrides.css
Normal file
@@ -0,0 +1,17 @@
|
||||
/* TAGS */
|
||||
|
||||
html {
|
||||
background-color: rgba(40, 44, 52, 0.64);
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgba(64, 64, 64, 0.0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* IDs */
|
||||
|
||||
|
||||
/* CLASSES */
|
@@ -1,25 +1,45 @@
|
||||
/* TAGS */
|
||||
|
||||
html {
|
||||
/*
|
||||
background-color: rgba(64, 64, 64, 0.64);
|
||||
background-color: rgb(40, 44, 52);
|
||||
*/
|
||||
background-color: rgba(40, 44, 52, 0.64);
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgba(64, 64, 64, 0.0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* IDs */
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
|
||||
.search-replace,
|
||||
.markdown-preview,
|
||||
.lsp-manager {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
background-color: rgba(64, 64, 64, 0.84);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.search-replace {
|
||||
bottom: 2em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.markdown-preview {
|
||||
top: 2em;
|
||||
bottom: 2em;
|
||||
right: 2em;
|
||||
z-index: 700;
|
||||
width: 50vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.lsp-manager {
|
||||
top: 2em;
|
||||
bottom: 2em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
|
||||
.info-bar {
|
||||
font-size: 0.8em;
|
||||
color: rgba(255, 255, 255, 0.84);
|
||||
@@ -42,18 +62,12 @@ body {
|
||||
border: 2px dashed lightblue;
|
||||
}
|
||||
|
||||
/*
|
||||
.hr-pane-handle {
|
||||
.vr-pane-handle {
|
||||
max-width: max-content;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.vr-pane-handle {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
*/
|
||||
|
||||
.vr-pane-handle {
|
||||
max-width: 0.05em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -68,10 +82,6 @@ body {
|
||||
}
|
||||
|
||||
.scroller {
|
||||
/*
|
||||
-webkit-scrollbar-color: #00000084 #ffffff64;
|
||||
scrollbar-color: #00000084 #ffffff64;
|
||||
*/
|
||||
-webkit-scrollbar-color: #00000084 #ffffff06;
|
||||
-webkit-scrollbar-width: thin;
|
||||
scrollbar-color: #00000084 #ffffff06;
|
||||
|
3
src/libs/showdown.min.js
vendored
Normal file
3
src/libs/showdown.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -29,6 +29,7 @@ declare global {
|
||||
openFiles: (arg0) => Promise<string>,
|
||||
saveFile: (arg0: any, arg1: any) => Promise<string>,
|
||||
saveFileAs: () => Promise<string>,
|
||||
chooseFolder: () => Promise<string>,
|
||||
closeFile: (arg0: any) => Promise<string>,
|
||||
getPathForFile: any,
|
||||
onLoadFiles: (arg0: any) => Promise<string>,
|
||||
|
1
src/typings.d.ts
vendored
Normal file
1
src/typings.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare var showdown: any;
|
@@ -13,4 +13,4 @@
|
||||
"src/polyfills.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
@@ -16,8 +16,11 @@
|
||||
"declaration": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
},
|
||||
"includes": [
|
||||
"src/typings.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +56,10 @@
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
},
|
||||
"includes": [
|
||||
"src/typings.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
*/
|
Reference in New Issue
Block a user