Skip to content

Commit

Permalink
fix: remaining dom bridge errors, firefox, gutter (#29)
Browse files Browse the repository at this point in the history
* fix: remaining dom bridge errors, firefox, gutter

Signed-off-by: Steven E Wright <StevenEWright@users.noreply.github.com>

* fix: address deepsource feedback

Signed-off-by: Steven E Wright <StevenEWright@users.noreply.github.com>

* fix: previous deepsource issue

Signed-off-by: Steven E Wright <StevenEWright@users.noreply.github.com>

---------

Signed-off-by: Steven E Wright <StevenEWright@users.noreply.github.com>
  • Loading branch information
StevenEWright authored Aug 17, 2023
1 parent 8f8fe70 commit f78be9c
Show file tree
Hide file tree
Showing 15 changed files with 501 additions and 194 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<p align="center"><a href="https://editor.kullna.org/"><img src="https://www.kullna.org/brand/logo.svg" width="150"></a></p>
<h1 align="center">@kullna/editor</h1>
<h3 align="center">A small but feature-rich code editor for the web</h3>
<p align="center"><img src="https://editor.kullna.org/assets/images/screenshot.png" width="724" alt="screenshot"></p>
<p align="center"><img src="https://editor.kullna.org/assets/images/screenshot.png" style="image-rendering: pixelated;" width="698" alt="screenshot"></p>
<p align="center">
<a href="https://editor.kullna.org/demo.html">🔍 Demos</a> •
<a href="https://editor.kullna.org/modules.html">📖 Docs</a> •
<a href="https://editor.kullna.org/pages/CONTRIBUTING.html">🙌 Contribute</a>
</p>
<p align="center">
<iframe src="https://ghbtns.com/github-btn.html?user=kullna&repo=editor&type=star&count=false&size=large" frameborder="0" scrolling="0" width="70" height="30" title="GitHub"></iframe>
</p>
<p align="center">
<a href="https://cdn.jsdelivr.net/npm/@kullna/editor/dist/kullna-editor.min.js">
<img src="https://img.shields.io/badge/CDN-JSDelivr-2aa198" alt="CDN">
Expand All @@ -18,9 +21,6 @@
<a href="https://www.gnu.org/licenses/lgpl-3.0">
<img src="https://img.shields.io/badge/License-LGPL_v3-b58900.svg" alt="License: LGPL v3">
</a>
<a href="https://github.com/kullna/editor">
<img src="https://img.shields.io/badge/Source-GitHub-d33682" alt="Source on GitHub">
</a>
<a href="https://www.codefactor.io/repository/github/kullna/editor">
<img src="https://www.codefactor.io/repository/github/kullna/editor/badge" alt="CodeFactor">
</a>
Expand Down Expand Up @@ -54,6 +54,7 @@ your feedback and contributions.
-**Undo/Redo**: Offers customizable undo/redo levels.
- ✂️ **Copy-Paste**: Ensure consistent cross-browser cut, copy, and paste operations in an
XSS-secure way.
- 🎁 **Wrapping**: Wrap text, and keep line numbers and highlights in sync.
- 🖊️ **Bracket Management**: Automatic close-bracket and close-quote insertion, with type-over
capability.
- ➡️ **Code Indentation**: Flexible code indentation using tab or shift-tab. Supports multi-line
Expand Down
Binary file modified assets/images/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 60 additions & 4 deletions demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@
margin-top: 5px;
}

.breakpoint-highlight {
background-color: #dc322f77;
border-top: 2px solid var(--sd-red);
border-bottom: 2px solid var(--sd-red);
opacity: 0.4;
position: absolute;
left: 0;
right: 0;
}

.code-gutter-highlight {
background-color: #ffffff22;
position: absolute;
Expand Down Expand Up @@ -155,13 +165,13 @@ <h3 align="center">A small but feature-rich code editor for the web</h3>
</main>

<!-- For local testing, comment this: -->
<script src="https://cdn.jsdelivr.net/npm/@kullna/editor/dist/kullna-editor.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/@kullna/editor/dist/kullna-editor.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>

<script type="module">
// For local testing, uncomment this:
// import * as KullnaEditor from './dist/kullna-editor.esm.js';
import * as KullnaEditor from './dist/kullna-editor.esm.js';

function demoWithOptions(parentSelector, id, titleText, options, defaultValue) {
const parent = document.querySelector(parentSelector);
Expand All @@ -179,14 +189,16 @@ <h3 align="center">A small but feature-rich code editor for the web</h3>
editor.highlightedLine = document.currentLineNumber;
});
editor.code = defaultValue;

return editor;
}

document.onreadystatechange = () => {
if (document.readyState !== 'complete') {
return;
}

demoWithOptions(
const standard = demoWithOptions(
'#demos',
'standard',
'Standard Configuration',
Expand All @@ -202,7 +214,7 @@ <h3 align="center">A small but feature-rich code editor for the web</h3>
class: 'code-gutter',
renderGutterLine: (lineNumber, gutterLineElement) => {
gutterLineElement.accessorySpan.innerHtml = '';
if (lineNumber === 5) {
if (lineNumber === 4) {
const breakpoint = document.createElement('div');
breakpoint.classList.add('breakpoint');
gutterLineElement.accessorySpan.appendChild(breakpoint);
Expand All @@ -223,6 +235,11 @@ <h3 align="center">A small but feature-rich code editor for the web</h3>
});
<\/script>`
);
standard.wrapsText = true;
const standardHighlight = standard.createHighlight();
standardHighlight.lineNumber = 4;
standardHighlight.visible = true;
standardHighlight.cssClass = 'breakpoint-highlight';

demoWithOptions(
'#demos',
Expand Down Expand Up @@ -280,6 +297,45 @@ <h3 align="center">A small but feature-rich code editor for the web</h3>
next
end function`
);

const noGutter = demoWithOptions(
'#demos',
'nogutter',
'No Gutter',
{
language: 'javascript',
highlightElement: e => {
hljs.highlightElement(e);
e.style.backgroundColor = 'transparent';
}
},
`const standard = demoWithOptions(
'#demos',
'standard',
'Standard Configuration',
{
language: 'html',
highlightElement: e => {
hljs.highlightElement(e);
e.style.backgroundColor = 'transparent';
},
gutter: {
border: true,
width: '55px',
class: 'code-gutter',
renderGutterLine: (lineNumber, gutterLineElement) => {
gutterLineElement.accessorySpan.innerHtml = '';
if (lineNumber === 4) {
const breakpoint = document.createElement('div');
breakpoint.classList.add('breakpoint');
gutterLineElement.accessorySpan.appendChild(breakpoint);
}
}
}
}
);`
);
noGutter.wrapsText = true;
};
</script>
<script
Expand Down
7 changes: 3 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ export function createEditor(
bracketProcessor: {
enabled: true
},
gutter: {},
...opt
};

Expand Down Expand Up @@ -153,13 +152,13 @@ export function createEditor(
const editorOptions: Partial<EditorOptions> = {
window: options.window,
dir: options.dir,
gutter: {
...options.gutter
},
highlight: options.highlightElement,
keydownPipeline,
keyupPipeline: options.keyupPipeline ?? []
};
if (options.gutter) {
editorOptions.gutter = options.gutter;
}

return new Editor(parent, editorOptions);
}
1 change: 1 addition & 0 deletions src/internals/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export class Editor

this.undoRedoManager = new UndoRedoManager(this);
this.view = new TextEditorView(options.window, parent, this);
this.view.gutterWidth = this.gutter ? this.gutter.width : '0px';
this.view.spellchecking = options.spellcheck;
this.view.dir = options.dir;
this.view.onLineMetricsChanged = metrics => {
Expand Down
56 changes: 23 additions & 33 deletions src/internals/gutter/gutter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export class Gutter {
* @param count The number of lines in the source text.
*/
setNumberOfLines(count: number) {
this.lineCount = count;
setNumberOfLines(this.element, count, this.options.renderGutterLine);
this.lineCount = Math.max(1, count);
setNumberOfLines(this.element, this.lineCount, this.options.renderGutterLine);
this.refreshStyles();
}

Expand Down Expand Up @@ -125,16 +125,33 @@ export class Gutter {
this.refreshStyles();
}

/**
* Gets or sets whether the gutter has a border or not.
*
* @returns Whether the gutter has a border or not.
*/
get width(): string {
return this.options.width;
}
set width(value: string) {
this.options.width = value;
this.refreshStyles();
}

constructor(opts: Partial<GutterOptions> = {}) {
this.options = {
border: false,
highlightedLine: -1,
dir: DEFAULT_DIR,
class: DEFAULT_CLASS,
width: DEFAULT_WIDTH,
...opts
};
this.element = createGutterElement(this.options);
this.element = document.createElement('div');
this.element.style.position = 'absolute';
this.element.style.zIndex = '50';
this.element.style.top = '0px';
this.element.style.bottom = '0px';
this.element.style.overflow = 'hidden';
this.setNumberOfLines(1);
this.refreshStyles();
}
Expand All @@ -151,6 +168,7 @@ export class Gutter {

/** Refreshes the styles of the gutter (reapplys the styles to the gutter elements). */
private refreshStyles() {
this.element.className = this.options.class ?? '';
if (this.options.dir === 'ltr') {
this.element.style.left = '0px';
this.element.style.right = 'unset';
Expand All @@ -174,6 +192,7 @@ export class Gutter {
this.element.style.borderRight = 'unset';
}
}
this.element.style.width = this.options.width;
}
}

Expand All @@ -187,35 +206,6 @@ interface GutterAnnotatedHTMLElement extends HTMLElement {
gutterLineElement?: GutterLineElement;
}

/**
* Constructs the DOM element for the gutter which holds the line number elements.
*
* @param opts The options for the gutter.
* @returns The DOM element for the specified gutter options.
*/
function createGutterElement(opts: GutterOptions): HTMLElement {
const gutter = document.createElement('div');
gutter.className = opts.class ?? '';
gutter.dir = opts.dir;
gutter.style.position = 'absolute';
gutter.style.top = '0px';
gutter.style.zIndex = '50';
gutter.style.bottom = '0px';
if (opts.dir === 'ltr') {
gutter.style.left = '0px';
gutter.style.right = 'unset';
gutter.style.textAlign = 'right';
} else {
gutter.style.right = '0px';
gutter.style.left = 'unset';
gutter.style.textAlign = 'left';
}
gutter.style.width = opts.width;
gutter.style.overflow = 'hidden';

return gutter;
}

/**
* Constructs the DOM elements for a single line in the gutter.
*
Expand Down
3 changes: 0 additions & 3 deletions src/internals/gutter/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ export interface GutterOptions {
*/
class?: string;

/** The line number to highlight. */
highlightedLine: number;

/** Whether or not to show a border on the gutter. */
border: boolean;

Expand Down
32 changes: 28 additions & 4 deletions src/internals/text_editor/dom/dom_bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ export class DomBridge {
*/
poll(): TextDocument {
const document = domToDocument(this.window, this.element);
// IF the browser doesn't support plaintext-only contenteditable, and the number of lines
// changed in the DOM; we need to push the entire document to the DOM because the browser
// will do some weird things with the DOM that we don't currently handle well.
// This is currently only the case in Firefox.
if (this.element.contentEditable === 'true') {
if (document.totalLines !== this._document.totalLines) {
documentToDom(this.element, document);
}
}
this.setDocumentWithChangeNotification(document);
return this.document;
}
Expand All @@ -140,10 +149,25 @@ export class DomBridge {
*/
recalculateLineMetrics() {
const lineContainers = Array.from(this.element.querySelectorAll('.text-document-line'));
const lineMetrics: LineMetric[] = lineContainers.map(lineContainer => ({
top: (lineContainer as HTMLElement).offsetTop,
height: (lineContainer as HTMLElement).offsetHeight
}));
const lineMetrics: LineMetric[] = [];
// When the user deletes a line and we aren't pushing the entire document to the DOM, we need to
// handle some of the cases where we run into the brower's built-in editing. For one, the line
// elements don't get automatically merged, so we need to do that ourselves.
// TODO: Merge instead of ignoring. Process br and \n as line breaks.
for (let i = 0; i < lineContainers.length; i++) {
const lineContainer = lineContainers[i] as HTMLElement;
if (
i !== lineContainers.length - 1 &&
(lineContainer.childNodes.length === 0 ||
(lineContainer.lastChild !== null && lineContainer.lastChild.nodeName !== 'BR'))
) {
continue;
}
lineMetrics.push({
top: lineContainer.offsetTop,
height: lineContainer.offsetHeight
});
}
this.setLineMetricsWithChangeNotification(lineMetrics);
}

Expand Down
Loading

0 comments on commit f78be9c

Please sign in to comment.