Skip to content

Commit

Permalink
fix: issue 36 + safari bug
Browse files Browse the repository at this point in the history
  • Loading branch information
motla committed Dec 27, 2023
1 parent 321c3ce commit 3ab45fb
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 18 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Vue 3

## v2.3.2

- Fix [Issue 36](https://github.com/motla/vue-document-editor/issues/26): Better handling of deleting on an empty page (no more user action is prevented on the keydown event, instead we deal with the result in the input event and ensure at least a `<div><br></div>` is always present in the content).
- Fix a Safari bug with the cursor, which appeared only on recent versions of Safari (`normalize()` must now be called before selection `<null>` tags removal)

## v2.3.1

- Fix [Issue 26](https://github.com/motla/vue-document-editor/issues/26)
Expand Down Expand Up @@ -80,6 +85,11 @@

# Vue 2

## v1.5.2

- Fix [Issue 36](https://github.com/motla/vue-document-editor/issues/26): Better handling of deleting on an empty page (no more user action is prevented on the keydown event, instead we deal with the result in the input event and ensure at least a `<div><br></div>` is always present in the content).
- Fix a Safari bug with the cursor, which appeared only on recent versions of Safari (`normalize()` must now be called before selection `<null>` tags removal)

## v1.5.1

- Fix [Issue 26](https://github.com/motla/vue-document-editor/issues/26)
Expand Down
2 changes: 1 addition & 1 deletion src/Demo/Demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default {
// This is where the pages content is stored and synced
content: [
// Every item below produce a page break
'<h1>Hello world!</h1><p>This is a rich-text editor built on top of <span contenteditable="false"><a href="https://vuejs.org/" target="_blank">Vue.js</a></span> using the native <span contenteditable="false"><a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content" target="_blank"><i>contenteditable</i></a></span> browser implementation and some JavaScript trickery to spread content over paper-sized pages.</p><p>Built-in functionality includes:</p><ul><li>Using Vue.js components as interactive page templates (see next page)</li><li>Word-by-word page splitting with forward and backward propagation (<u>still experimental</u>)</li><li>Native Print compatible</li><li>Dynamic document format and margins in millimeters</li><li>Custom page overlays (headers, footers, page numbers)</li><li>Page breaks</li><li>Smart zoom and page display modes</li><li>Computes text style at caret position</li></ul><p>This library may be useful if you design an application that generate documents and you would let the user to modify them slightly before printing / saving, but with limited / interactive possibilities. It does not intend to replace a proper document editor with full functionality.<br>Make sure this project is suitable to your needs before using it.</p><p>This demo adds:</p><ul><li>The top bar (<span contenteditable="false"><a href="https://github.com/motla/vue-file-toolbar-menu" target="_blank">vue-file-toolbar-menu</a></span> component) and the functions associated with it</li><li>Rewritten history stack (undo/redo) compatible with native commands</li><li>Pinch and trackpad zooming</li></ul><p>Check out the <span contenteditable="false"><a href="https://github.com/motla/vue-document-editor/blob/master/src/Demo/Demo.vue" target="_blank">Demo.vue</a></span> file if you need to add these functionalities to your application.</p><p>The link below is an example of non-editable block set with <code>contenteditable="false"</code>:</p><p style="text-align:center" contenteditable="false"><a href="https://github.com/motla/vue-document-editor">View docs on Github</a>, you can\'t edit me.</p><p>But you can still edit this.</p>',
'<h1>Hello world!</h1><p>This is a rich-text editor built on top of <span contenteditable="false"><a href="https://vuejs.org/" target="_blank">Vue.js</a></span> using the native <span contenteditable="false"><a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content" target="_blank"><i>contenteditable</i></a></span> browser implementation and some JavaScript trickery to spread content over paper-sized pages.</p><p>Built-in functionality includes:</p><ul><li>Using Vue.js components as interactive page templates (see next page)</li><li>Word-by-word page splitting (<u>still experimental - only for plain HTML content</u>)</li><li>Native Print compatible</li><li>Dynamic document format and margins in millimeters</li><li>Custom page overlays (headers, footers, page numbers)</li><li>Page breaks</li><li>Smart zoom and page display modes</li><li>Computes text style at caret position</li></ul><p>This library may be useful if you design an application that generate documents and you would let the user to modify them slightly before printing / saving, but with limited / interactive possibilities. It does not intend to replace a proper document editor with full functionality.<br>Make sure this project is suitable to your needs before using it.</p><p>This demo adds:</p><ul><li>The top bar (<span contenteditable="false"><a href="https://github.com/motla/vue-file-toolbar-menu" target="_blank">vue-file-toolbar-menu</a></span> component) and the functions associated with it</li><li>Rewritten history stack (undo/redo) compatible with native commands</li><li>Pinch and trackpad zooming</li></ul><p>Check out the <span contenteditable="false"><a href="https://github.com/motla/vue-document-editor/blob/master/src/Demo/Demo.vue" target="_blank">Demo.vue</a></span> file if you need to add these functionalities to your application.</p><p>The link below is an example of non-editable block set with <code>contenteditable="false"</code>:</p><p style="text-align:center" contenteditable="false"><a href="https://github.com/motla/vue-document-editor">View docs on Github</a>, you can\'t edit me.</p><p>But you can still edit this.</p>',
{ template: markRaw(InvoiceTemplate), props: { invoice_number: "AB38052985" } },
'<br><br><h1>Headers / footers example</h1><br>Page numbers have been added on every page of this document.<br>Header and footer overlays will be added from page 3 to all subsequent ones.<br><br>Check out the <code>overlay</code> method of the <span contenteditable="false"><a href="https://github.com/motla/vue-document-editor/blob/master/src/Demo/Demo.vue" target="_blank">Demo.vue</a></span> file to customize this.',
'<h1>«</h1><div style="width:80%; text-align:justify; margin:auto"><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.</p><p>Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.</p><p>Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet.</p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.</p><p>Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.</p><p>Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet.</p></div><h1 style="text-align:right">»</h1>',
Expand Down
28 changes: 11 additions & 17 deletions src/DocumentEditor/DocumentEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</div>

<!-- Document editor -->
<div class="content" ref="content" :contenteditable="editable" :style="page_style(-1)" @input="input" @keyup="process_current_text_style" @keydown="keydown">
<div class="content" ref="content" :contenteditable="editable" :style="page_style(-1)" @input="input" @keyup="process_current_text_style">
<!-- This is a Vue "hoisted" static <div> which contains every page of the document and can be modified by the DOM -->
</div>

Expand Down Expand Up @@ -148,7 +148,7 @@ export default {
for(const page of this.pages) {
// set raw HTML content
if(!this.content[page.content_idx]) page.elt.innerHTML = "<div><br></div>";
if(!this.content[page.content_idx]) page.elt.innerHTML = "<div><br></div>"; // ensure empty pages are filled with at least <div><br></div>, otherwise editing fails on Chrome
else if(typeof this.content[page.content_idx] == "string") page.elt.innerHTML = "<div>"+this.content[page.content_idx]+"</div>";
else if(page.template) {
const componentElement = defineCustomElement(page.template);
Expand Down Expand Up @@ -189,6 +189,7 @@ export default {
// If all the document was wiped out, start a new empty document
if(!this.pages.length){
this.fit_in_progress = false; // clear "fit in progress" flag
this.$emit("update:content", [""]);
return;
}
Expand Down Expand Up @@ -252,6 +253,10 @@ export default {
this.update_pages_elts();
}
// Normalize pages HTML content
for(const page of this.pages) {
if(!page.template) page.elt.normalize(); // normalize HTML (merge text nodes) - don't touch template pages or it can break Vue
}
// Restore selection and remove empty elements
if(document.body.contains(start_marker)){
Expand All @@ -264,9 +269,8 @@ export default {
if(start_marker.parentElement) start_marker.parentElement.removeChild(start_marker);
if(end_marker.parentElement) end_marker.parentElement.removeChild(end_marker);
// Normalize and store current page HTML content
// Store pages HTML content
for(const page of this.pages) {
if(!page.template) page.elt.normalize(); // normalize HTML (merge text nodes) - don't touch template pages or it can break Vue
page.prev_innerHTML = page.elt.innerHTML; // store current pages innerHTML for next call
}
Expand All @@ -282,16 +286,6 @@ export default {
if(e.inputType != "insertText") this.process_current_text_style(); // update current style if it has changed
},
// Keydown event
keydown (e) {
// if the document is empty, prevent removing the first page container with a backspace input (keycode 8)
// which is now the default behavior for web browsers
if(e.keyCode == 8 && this.content.length <= 1 && typeof(this.content[0]) == "string") {
const text = this.content[0].replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
if(!text) e.preventDefault();
}
},
// Emit content change to parent
emit_new_content () {
let removed_pages_flag = false; // flag to call reset_content if some pages were removed by the user
Expand All @@ -314,12 +308,12 @@ export default {
while(elt.children.length == 1 && elt.firstChild.tagName && elt.firstChild.tagName.toLowerCase() == "div" && !elt.firstChild.getAttribute("style")) {
elt = elt.firstChild;
}
return elt.innerHTML;
}).join('') || false;
return (elt.innerHTML == "<br>" ? "" : elt.innerHTML); // treat a page containing a single <br> as an empty content
}).join('');
}
// if item is a component, just clone the item
else return { template: item.template, props: { ...item.props }};
}).filter(item => (item != false)); // remove empty items
}).filter(item => (item !== false)); // remove empty items
// avoid calling reset_content after the parent content is updated (infinite loop)
if(!removed_pages_flag) this.prevent_next_content_update_from_parent = true;
Expand Down

0 comments on commit 3ab45fb

Please sign in to comment.