Skip to content

Commit a577557

Browse files
committed
- Plugin wrapper implemented for abstraction editor operations
- new language: SH - Standard menu items added (undo, redo, copy etc..)
1 parent 98f4f1f commit a577557

11 files changed

Lines changed: 190 additions & 75 deletions

File tree

linux_clean_install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Script to quickly fresh-install note-manager
22

33
# 1- Remove existing installation if exists
4-
# 2- Delete 'out' folder
4+
# 2- Delete 'out' folder if exists
55
# 3- Run 'npm make' to create new artifact
66
# 4- Install the newly created artifact
77
# 5- Launch the app

src/domain/AceEditorWrapper.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {EditorWrapper} from "./EditorWrapper";
2+
import {Ace} from "ace-builds";
3+
import {LanguageName} from "./SupportedLanguage";
4+
5+
export class AceEditorWrapper implements EditorWrapper {
6+
private _editor: Ace.Editor;
7+
8+
constructor(editor: Ace.Editor) {
9+
this._editor = editor;
10+
}
11+
12+
getAllSelectionRanges(): Array<Ace.Range> {
13+
const multiCursorRanges = this._editor?.getSelection().getAllRanges();
14+
15+
if(multiCursorRanges?.length > 0) {
16+
return multiCursorRanges;
17+
} else {
18+
const singleSelectionRange = this.getSingleSelectionRange();
19+
20+
return singleSelectionRange ? [singleSelectionRange] : [];
21+
}
22+
}
23+
24+
getSingleSelectionRange(): Ace.Range {
25+
return this._editor.getSelectionRange();
26+
}
27+
28+
replaceRange(range: Ace.Range, text: string): void {
29+
if(!range) return;
30+
31+
this._editor?.session.replace(range, text);
32+
}
33+
34+
getSelectedText() {
35+
return this._editor?.getSelectedText() || "";
36+
}
37+
38+
replaceAllSelectionRanges(text: string): void {
39+
const ranges = this.getAllSelectionRanges();
40+
41+
if(ranges?.length > 0) { // multi cursor
42+
for(let range of ranges) {
43+
this.replaceRange(range, text);
44+
}
45+
}
46+
}
47+
48+
replaceSelection(text: string): void {
49+
const selectionRange = this.getSingleSelectionRange();
50+
51+
this.replaceRange(selectionRange, text);
52+
}
53+
54+
getLanguage(): LanguageName {
55+
return this._editor?.getOption("mode").split("/").pop() as LanguageName || LanguageName.TEXT;
56+
}
57+
58+
getTextRange(range: Ace.Range): string {
59+
return this._editor?.session.getTextRange(range) || "";
60+
}
61+
62+
getValue(): string {
63+
return this._editor?.session.getValue();
64+
}
65+
}

src/domain/EditorWrapper.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {LanguageName} from "./SupportedLanguage";
2+
3+
export interface EditorWrapper {
4+
/**
5+
* Returns the selection range in a single cursor editor.
6+
*/
7+
getSingleSelectionRange: () => any;
8+
9+
/**
10+
* Returns the array of selection ranges in a multi cursor editor.
11+
*/
12+
getAllSelectionRanges: () => Array<any>;
13+
14+
/**
15+
* Replaces single cursor selection with given text.
16+
* @param text
17+
*/
18+
replaceSelection: (text: string) => void,
19+
20+
/**
21+
* Replaces the content in the given range with provided string.
22+
* @param range
23+
* @param text
24+
*/
25+
replaceRange: (range: any, text: string) => void,
26+
27+
/**
28+
* Replaces the contents of each selection range with the given string. Supports both single cursor and multiple cursors.
29+
* @param text
30+
*/
31+
replaceAllSelectionRanges: (text: string) => void
32+
33+
/**
34+
* Returns the selected text. If the editor is in multi cursor mode, returns the concatenation of the all selections.
35+
*/
36+
getSelectedText: () => string;
37+
38+
/**
39+
* Returns the language mode of the editor.
40+
*/
41+
getLanguage: () => LanguageName;
42+
43+
/**
44+
* Returns the text that is included by the given range.
45+
* @param range
46+
*/
47+
getTextRange: (range: any) => string;
48+
49+
/**
50+
* Returns the current value of the editor.
51+
*/
52+
getValue: () => string;
53+
}

src/domain/SupportedLanguage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum LanguageName {
1414
CSS = "css",
1515
MARKDOWN = "markdown",
1616
JAVA = "java",
17+
SH = "sh"
1718
}
1819

1920
export const SupportedLanguages: Record<LanguageName, SupportedLanguage> = {
@@ -62,6 +63,11 @@ export const SupportedLanguages: Record<LanguageName, SupportedLanguage> = {
6263
label: "Java",
6364
extensions: [".java", ".class"]
6465
},
66+
[LanguageName.SH]: {
67+
name: LanguageName.SH,
68+
label: "SH",
69+
extensions: [".sh"]
70+
}
6571
};
6672

6773
export function findLanguageByFileName(fileName: string|undefined): SupportedLanguage {

src/main.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,6 @@ const createWindow = () => {
5959
// @ts-ignore
6060
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
6161

62-
mainWindow.webContents.on('will-navigate', (event, url) => {
63-
console.log(`Blocked navigation to: ${url}`);
64-
event.preventDefault(); // Prevent navigation
65-
});
66-
67-
// Intercept link clicks that try to open new windows
68-
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
69-
console.log(`Blocked new window for: ${url}`);
70-
return { action: 'deny' }; // Prevent the new window from opening
71-
});
72-
7362
attachTitlebarToWindow(mainWindow);
7463

7564
mainWindow.webContents.on("dom-ready", async () => {

src/react/components/editor/Editor.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {fireEvent} from "../../ApplicationEvents";
99
import {EventType} from "../../../enums";
1010
import {allPlugins} from "../../../utils/PluginLoader";
1111
import {ipcRenderer} from "electron";
12+
import {AceEditorWrapper} from "../../../domain/AceEditorWrapper";
1213

1314
export default function Editor({content, language, onEditorLoad, changeListener, cursorListener, selectionListener}: {
1415
content: string,
@@ -60,7 +61,12 @@ export default function Editor({content, language, onEditorLoad, changeListener,
6061

6162
const handleLoad = (editor: Ace.Editor) => {
6263
try {
63-
allPlugins.forEach(p => p.initializePlugin(editor));
64+
const editorWrapper = new AceEditorWrapper(editor);
65+
66+
allPlugins.forEach(p => {
67+
console.log("initializing plugin... ("+p.name+")");
68+
p.initializePlugin(editorWrapper)
69+
});
6470
} catch (err) {
6571
console.error("could not initialize plugin: " + err);
6672
}
@@ -117,7 +123,10 @@ export default function Editor({content, language, onEditorLoad, changeListener,
117123
showPrintMargin: false,
118124
useWorker: false,
119125
enableSnippets: true,
120-
cursorStyle: "ace"
126+
cursorStyle: "ace",
127+
scrollPastEnd: false,
128+
useSoftTabs: false,
129+
navigateWithinSoftTabs: false,
121130
}}
122131
width={"100%"}
123132
height={"100%"}

src/react/components/editor/plugins/Base64Plugin.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {EditorAction, EditorMenuItem, EditorPlugin} from "./index";
2-
import {Ace} from "ace-builds";
2+
import {EditorWrapper} from "../../../../domain/EditorWrapper";
33

44
enum Keys {
55
B64_ENCODE = "Base64_base64Encode",
@@ -28,7 +28,7 @@ export default class Base64Plugin implements EditorPlugin {
2828
]
2929
}
3030
];
31-
editor?: Ace.Editor
31+
editor?: EditorWrapper
3232

3333
doAction(code: string): void {
3434
if(!this.editor) throw new Error("Plugin not initialized !");
@@ -43,7 +43,7 @@ export default class Base64Plugin implements EditorPlugin {
4343
return Array.from(this.actionMap.keys());
4444
}
4545

46-
initializePlugin(editor: Ace.Editor): void {
46+
initializePlugin(editor: EditorWrapper): void {
4747
this.editor = editor;
4848

4949
this.initializeActionMap();
@@ -53,16 +53,36 @@ export default class Base64Plugin implements EditorPlugin {
5353
this.actionMap.set(Keys.B64_ENCODE, {
5454
label: "Base64 Encode",
5555
code: Keys.B64_ENCODE,
56-
perform: (editor: Ace.Editor) => {
57-
editor.session.replace(editor.getSelectionRange(), encode(editor.getSelectedText()));
56+
perform: (editor: EditorWrapper) => {
57+
const selectionRanges = editor.getAllSelectionRanges();
58+
59+
if(selectionRanges && selectionRanges.length > 0 && (editor.getSelectedText() || "").length > 0) {
60+
for(let range of selectionRanges) {
61+
const target = editor.getTextRange(range);
62+
63+
target && editor.replaceRange(range, encode(target));
64+
}
65+
66+
return;
67+
}
5868
}
5969
});
6070

6171
this.actionMap.set(Keys.B64_DECODE, {
6272
label: "Base64 Decode",
6373
code: Keys.B64_DECODE,
64-
perform: (editor: Ace.Editor) => {
65-
editor.session.replace(editor.getSelectionRange(), decode(editor.getSelectedText()));
74+
perform: (editor: EditorWrapper) => {
75+
const selectionRanges = editor.getAllSelectionRanges();
76+
77+
if(selectionRanges && selectionRanges.length > 0 && (editor.getSelectedText() || "").length > 0) {
78+
for(let range of selectionRanges) {
79+
const target = editor.getTextRange(range);
80+
81+
target && editor.replaceRange(range, decode(target));
82+
}
83+
84+
return;
85+
}
6686
}
6787
});
6888
}

src/react/components/editor/plugins/BeautifyPlugin.ts

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {EditorAction, EditorMenuItem, EditorPlugin} from "./index";
2-
import {Ace} from "ace-builds";
32
import {LanguageName} from "../../../../domain/SupportedLanguage";
43
import xmlFormat from "xml-formatter";
54
import os from "os";
5+
import {EditorWrapper} from "../../../../domain/EditorWrapper";
66

77
enum Keys {
88
FORMAT = "beautify.format",
@@ -28,7 +28,7 @@ export default class BeautifyPlugin implements EditorPlugin{
2828

2929
];
3030

31-
editor?: Ace.Editor;
31+
editor?: EditorWrapper;
3232

3333
actionMap?: Map<string, EditorAction>;
3434

@@ -47,7 +47,7 @@ export default class BeautifyPlugin implements EditorPlugin{
4747
return Array.from(this.actionMap.keys());
4848
}
4949

50-
initializePlugin(editor: Ace.Editor): void {
50+
initializePlugin(editor: EditorWrapper): void {
5151
this.editor = editor;
5252

5353
this.initializeActionMap();
@@ -60,32 +60,18 @@ export default class BeautifyPlugin implements EditorPlugin{
6060
label: "Format",
6161
code: Keys.FORMAT,
6262
perform: () => {
63-
let langName = this.editor?.getOption("mode").split("/").pop() || LanguageName.TEXT;
63+
let langName = this.editor?.getLanguage() || LanguageName.TEXT;
6464

65-
// multi selection
66-
const multiCursorRanges = this.editor?.getSelection().getAllRanges();
67-
if(multiCursorRanges && multiCursorRanges.length > 0 && (this.editor?.getSelectedText() || "").length > 0) {
68-
for(let range of multiCursorRanges) {
69-
const target = this.editor?.session.getTextRange(range) || "";
65+
const selectionRanges = this.editor?.getAllSelectionRanges();
66+
if(selectionRanges && selectionRanges.length > 0 && (this.editor?.getSelectedText() || "").length > 0) {
67+
for(let range of selectionRanges) {
68+
const target = this.editor?.getTextRange(range) || "";
7069

71-
this.editor?.session.replace(range, format(langName, target));
70+
this.editor?.replaceRange(range, format(langName, target));
7271
}
7372

7473
return;
7574
}
76-
77-
// single selection
78-
const singleCursorRange = this.editor?.getSelection().getRange();
79-
if(singleCursorRange && (this.editor?.getSelectedText() || "").length > 0) {
80-
const target = this.editor?.session.getTextRange(singleCursorRange) || "";
81-
82-
this.editor?.session.replace(singleCursorRange, format(langName, target));
83-
84-
return;
85-
}
86-
87-
// no selection
88-
this.editor?.setValue(format(langName, this.editor?.getValue()));
8975
}
9076
});
9177
}

src/react/components/editor/plugins/PreviewPlugin.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {EditorMenuItem, EditorPlugin, ToolbarMenuItem} from "./index";
2-
import {Ace} from "ace-builds";
32
import {LanguageName} from "../../../../domain/SupportedLanguage";
43
import DOMPurify from "dompurify";
54
import {marked} from "marked";
5+
import {EditorWrapper} from "../../../../domain/EditorWrapper";
66

77
const previewIcon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4xIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTQ0OCA4MGM4LjggMCAxNiA3LjIgMTYgMTZsMCAzMTkuOC01LTYuNS0xMzYtMTc2Yy00LjUtNS45LTExLjYtOS4zLTE5LTkuM3MtMTQuNCAzLjQtMTkgOS4zTDIwMiAzNDAuN2wtMzAuNS00Mi43QzE2NyAyOTEuNyAxNTkuOCAyODggMTUyIDI4OHMtMTUgMy43LTE5LjUgMTAuMWwtODAgMTEyTDQ4IDQxNi4zbDAtLjNMNDggOTZjMC04LjggNy4yLTE2IDE2LTE2bDM4NCAwek02NCAzMkMyOC43IDMyIDAgNjAuNyAwIDk2TDAgNDE2YzAgMzUuMyAyOC43IDY0IDY0IDY0bDM4NCAwYzM1LjMgMCA2NC0yOC43IDY0LTY0bDAtMzIwYzAtMzUuMy0yOC43LTY0LTY0LTY0TDY0IDMyem04MCAxOTJhNDggNDggMCAxIDAgMC05NiA0OCA0OCAwIDEgMCAwIDk2eiIvPjwvc3ZnPg==";
88
const refreshIcon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4xIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTQ2My41IDIyNGw4LjUgMGMxMy4zIDAgMjQtMTAuNyAyNC0yNGwwLTEyOGMwLTkuNy01LjgtMTguNS0xNC44LTIyLjJzLTE5LjMtMS43LTI2LjIgNS4yTDQxMy40IDk2LjZjLTg3LjYtODYuNS0yMjguNy04Ni4yLTMxNS44IDFjLTg3LjUgODcuNS04Ny41IDIyOS4zIDAgMzE2LjhzMjI5LjMgODcuNSAzMTYuOCAwYzEyLjUtMTIuNSAxMi41LTMyLjggMC00NS4zcy0zMi44LTEyLjUtNDUuMyAwYy02Mi41IDYyLjUtMTYzLjggNjIuNS0yMjYuMyAwcy02Mi41LTE2My44IDAtMjI2LjNjNjIuMi02Mi4yIDE2Mi43LTYyLjUgMjI1LjMtMUwzMjcgMTgzYy02LjkgNi45LTguOSAxNy4yLTUuMiAyNi4yczEyLjUgMTQuOCAyMi4yIDE0LjhsMTE5LjUgMHoiLz48L3N2Zz4=";
@@ -29,11 +29,11 @@ export default class PreviewPlugin implements EditorPlugin {
2929
if(refreshButton && content) {
3030
const shadow = content.attachShadow({mode: 'open'});
3131

32-
shadow.innerHTML = preview(this.editor?.getValue(), getLanguage(this.editor)) || getEmptyPreviewResult();
32+
shadow.innerHTML = preview(this.editor?.getValue(), this.editor?.getLanguage()) || getEmptyPreviewResult();
3333

3434
refreshButton.addEventListener("click", () => {
3535
shadow.innerHTML = "";
36-
shadow.innerHTML = preview(this.editor?.getValue(), getLanguage(this.editor)) || getEmptyPreviewResult();
36+
shadow.innerHTML = preview(this.editor?.getValue(), this.editor?.getLanguage()) || getEmptyPreviewResult();
3737
});
3838
}
3939
},
@@ -107,7 +107,7 @@ export default class PreviewPlugin implements EditorPlugin {
107107
}
108108
];
109109

110-
editor?: Ace.Editor;
110+
editor?: EditorWrapper;
111111

112112
doAction(code: string): void {
113113
if(!this.editor) throw new Error("Plugin not initialized !");
@@ -125,17 +125,11 @@ export default class PreviewPlugin implements EditorPlugin {
125125
return Object.keys(Keys);
126126
}
127127

128-
initializePlugin(editor: Ace.Editor): void {
128+
initializePlugin(editor: EditorWrapper): void {
129129
this.editor = editor;
130130
}
131131
}
132132

133-
function getLanguage(editor?: Ace.Editor): string {
134-
if(!editor) return LanguageName.TEXT;
135-
136-
return editor?.getOption("mode").split("/").pop() || LanguageName.TEXT;
137-
}
138-
139133
function preview(content?: string, lang?: string) {
140134
if(!content || !lang) return "";
141135

0 commit comments

Comments
 (0)