-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor Editor Constructor and Methods, Write Editor Getters #9110
Changes from 6 commits
5dc442c
1bc9f25
a61264c
d1bb02b
8ef8d40
1eabf4f
da777d0
01f7fca
6c7ea4d
fde3b9f
f3dae9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,19 @@ | ||
// jQuery (document).ready function: | ||
class Editor { | ||
// default parameters: | ||
// defaultForm - when the Editor is initialized, there needs to be a default editor form: | ||
// 1. the main comment form in multi-comment wikis, questions, & research notes. | ||
// 2. the only editor form on /wiki/new and /wiki/edit | ||
// elementIDPrefixes - the ID naming conventions are different depending on context: | ||
// 1. multi-form pages with multiple comments: #comment-preview-123 | ||
// 2. /wiki/new and /wiki/edit: #preview-main | ||
constructor(defaultForm = "main", elementIDPrefixes = {}) { | ||
this.commentFormID = defaultForm; | ||
this.elementIDPrefixes = elementIDPrefixes; | ||
this.textarea = $("#text-input-" + this.commentFormID); | ||
|
||
$E = { | ||
initialize: function() { | ||
// call setState with no parameters, aka. default parameters. | ||
// default parameters point toward either: | ||
// 1. the comment form at the bottom of multi-comment wikis/questions/research notes | ||
// 2. the only editor form on /wiki/new and /wiki/edit | ||
$E.setState(); | ||
|
||
// this will get deleted in the next few PRs, so collapsing into one line to pass codeclimate | ||
this.templates = { 'blog': "## The beginning\n\n## What we did\n\n## Why it matters\n\n## How can you help", 'default': "## What I want to do\n\n## My attempt and results\n\n## Questions and next steps\n\n## Why I'm interested", 'support': "## Details about the problem\n\n## A photo or screenshot of the setup", 'event': "## Event details\n\nWhen, where, what\n\n## Background\n\nWho, why", 'question': "## What I want to do or know\n\n## Background story" }; | ||
|
||
marked.setOptions({ | ||
gfm: true, | ||
tables: true, | ||
|
@@ -23,131 +29,119 @@ $E = { | |
return code; | ||
} | ||
}); | ||
}, | ||
setState: function(textarea = 'text-input', preview = 'comment-preview-main', title = 'title') { | ||
$E.title = $('#' + title + 'title'); // not sure why this exists? seems like $E.title is always #title | ||
$E.textarea = $('#' + textarea); | ||
} | ||
setState(commentFormID) { | ||
this.commentFormID = commentFormID; | ||
this.textarea = $("#text-input-" + commentFormID); | ||
$E.textarea.bind('input propertychange', $E.save); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed most instances of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to explain this a little further, it's because when |
||
$E.preview = $('#' + preview); | ||
}, | ||
is_editing: function() { | ||
return ($E.textarea[0].selectionStart == 0 && $E.textarea[0].selectionEnd == 0) | ||
}, | ||
refresh: function() { | ||
// textarea | ||
$E.textarea = ($D.selected).find('textarea').eq(0); | ||
$E.textarea.bind('input propertychange',$E.save); | ||
// preview | ||
$E.preview = ($D.selected).find('.comment-preview').eq(0); | ||
}, | ||
isRichTextEditor: function(url) { | ||
// this RegEx matches three different cases where the legacy editor is still used: | ||
// 1. /wiki/new | ||
// 2. /wiki/{wiki name}/edit | ||
// 3. /features/new | ||
const legacyEditorPath = RegExp(/\/(wiki|features)(\/[^\/]+\/edit|\/new)/); | ||
return !legacyEditorPath.test(url); // if we're not on one of these pages, we are using the rich-text editor. | ||
}, | ||
// wraps currently selected text in textarea with strings a and b | ||
wrap: function(a, b, args) { | ||
// we only refresh $E's values if we are on a page using the rich-text editor (most pages). | ||
// the legacy editor pages only have one editor form, unlike pages with multiple comments. | ||
if (this.isRichTextEditor(window.location.pathname)) { this.refresh(); } | ||
var len = $E.textarea.val().length; | ||
var start = $E.textarea[0].selectionStart; | ||
var end = $E.textarea[0].selectionEnd; | ||
const fallbackParameterExists = args && args['fallback']; | ||
const newlineParameterExists = args && args['newline']; | ||
var sel = fallbackParameterExists ? args['fallback'] : $E.textarea.val().substring(start, end); // // fallback if nothing has been selected, and we're simply dealing with an insertion point | ||
var replace = a + sel + b; | ||
if (newlineParameterExists) { | ||
replace = replace + "\n\n"; | ||
} | ||
if (newlineParameterExists && $E.textarea[0].selectionStart > 0) { | ||
replace = "\n" + replace; | ||
} | ||
get textAreaElement() { | ||
const textAreaID = "#text-input-" + this.commentFormID; | ||
return $(textAreaID); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New getter functions! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oohhhhh! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, are these transpiled out in the asset pipeline? Any worries about internet explorer? I guess not these days! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get I mean, i guess it's still 5% of usage: https://www.netmarketshare.com/browser-market-share.aspx?qprid=2&qpcustomd=0 what do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh huh... That's a good point. I hadn't checked this one out in MDN. I think they can be rewritten as regular methods! That won't be too hard. Can do it in another PR although... Do our users really use IE to access the website? Better safe than sorry I guess. |
||
get textAreaValue() { | ||
return this.textAreaElement.val(); | ||
} | ||
get previewElement() { | ||
let previewIDPrefix = "#comment-preview-"; | ||
if (this.elementIDPrefixes.hasOwnProperty("preview")) { | ||
// eg. on /wiki/new & /wiki/edit, the preview element is called #preview-main | ||
previewIDPrefix = "#" + this.elementIDPrefixes["preview"] + "-"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of the main changes in this PR. Honestly, I have mixed feelings about it since it's a little weird, especially in the constructor. But I do think it's an okay workaround. I made this because there was some tension in having I wanted preview elements to be named So, this is my workaround. It's a little weird, but thanks to OOP, it can be scaled back or revised much easier. |
||
} | ||
$E.textarea.val($E.textarea.val().substring(0,start) + replace + $E.textarea.val().substring(end,len)); | ||
}, | ||
bold: function() { | ||
$E.wrap('**','**') | ||
}, | ||
italic: function() { | ||
$E.wrap('_','_') | ||
}, | ||
link: function(uri) { | ||
const previewID = previewIDPrefix + this.commentFormID; | ||
return $(previewID); | ||
} | ||
// wraps currently selected text in textarea with strings a and b | ||
wrap(a, b, newlineDesired = false, fallback) { | ||
const selectionStart = this.textAreaElement[0].selectionStart; | ||
const selectionEnd = this.textAreaElement[0].selectionEnd; | ||
const selection = fallback || this.textAreaValue.substring(selectionStart, selectionEnd); // fallback if nothing has been selected, and we're simply dealing with an insertion point | ||
|
||
let newText = a + selection + b; // ie. ** + selection + ** (wrapping selection in bold) | ||
if (newlineDesired) { newText = newText + "\n\n"; } | ||
const selectionStartsMidText = this.textAreaElement[0].selectionStart > 0; | ||
if (newlineDesired && selectionStartsMidText) { newText = "\n" + newText; } | ||
|
||
const textLength = this.textAreaValue.length; | ||
const textBeforeSelection = this.textAreaValue.substring(0, selectionStart); | ||
const textAfterSelection = this.textAreaValue.substring(selectionEnd, textLength); | ||
this.textAreaElement.val(textBeforeSelection + newText + textAfterSelection); | ||
} | ||
bold() { | ||
this.wrap('**', '**'); | ||
} | ||
italic() { | ||
this.wrap('_', '_'); | ||
} | ||
link(uri) { | ||
uri = prompt('Enter a URL'); | ||
if (uri === null) { uri = ""; } | ||
noi5e marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$E.wrap('[', '](' + uri + ')'); | ||
}, | ||
image: function(src) { | ||
$E.wrap('\n\n') | ||
}, | ||
h1: function() { | ||
$E.wrap('#','') | ||
}, | ||
h2: function() { | ||
$E.wrap('##','') | ||
}, | ||
h3: function() { | ||
$E.wrap('###','') | ||
}, | ||
h4: function() { | ||
$E.wrap('####','') | ||
}, | ||
h5: function() { | ||
$E.wrap('#####','') | ||
}, | ||
h6: function() { | ||
$E.wrap('######','') | ||
}, | ||
h7: function() { | ||
$E.wrap('#######','') | ||
}, | ||
this.wrap( | ||
'[', | ||
'](' + uri + ')' | ||
); | ||
} | ||
image(src) { | ||
this.wrap( | ||
'\n\n' | ||
); | ||
} | ||
// these header formatting functions are not used anywhere, so commenting them out for now to pass codeclimate: | ||
|
||
// h1() { | ||
// this.wrap('#','') | ||
// } | ||
h2() { | ||
this.wrap('##', ''); | ||
} | ||
// h3() { | ||
// this.wrap('###','') | ||
// } | ||
// h4() { | ||
// this.wrap('####','') | ||
// } | ||
// h5() { | ||
// this.wrap('#####','') | ||
// } | ||
// h6() { | ||
// this.wrap('######','') | ||
// } | ||
// h7() { | ||
// this.wrap('#######','') | ||
// } | ||
// this function is dedicated to Don Blair https://github.com/donblair | ||
save: function() { | ||
localStorage.setItem('plots:lastpost',$E.textarea.val()) | ||
localStorage.setItem('plots:lasttitle',$E.title.val()) | ||
}, | ||
recover: function() { | ||
$E.textarea.val(localStorage.getItem('plots:lastpost')) | ||
$E.title.val(localStorage.getItem('plots:lasttitle')) | ||
}, | ||
apply_template: function(template) { | ||
if($E.textarea.val() == ""){ | ||
$E.textarea.val($E.templates[template]) | ||
}else if(($E.textarea.val() == $E.templates['event']) || ($E.textarea.val() == $E.templates['default']) || ($E.textarea.val() == $E.templates['support'])){ | ||
$E.textarea.val($E.templates[template]) | ||
save() { | ||
localStorage.setItem('plots:lastpost', this.textAreaValue); | ||
} | ||
recover() { | ||
this.textAreaElement.val(localStorage.getItem('plots:lastpost')); | ||
} | ||
apply_template(template) { | ||
if(this.textAreaValue == ""){ | ||
this.textAreaElement.val(this.templates[template]) | ||
}else if((this.textAreaValue == this.templates['event']) || (this.textAreaValue == this.templates['default']) || (this.textAreaValue == this.templates['support'])){ | ||
this.textAreaElement.val(this.templates[template]) | ||
}else{ | ||
$E.textarea.val($E.textarea.val()+'\n\n'+$E.templates[template]) | ||
this.textAreaElement.val(this.textAreaValue+'\n\n'+this.templates[template]) | ||
} | ||
}, | ||
templates: { | ||
'blog': "## The beginning\n\n## What we did\n\n## Why it matters\n\n## How can you help", | ||
'default': "## What I want to do\n\n## My attempt and results\n\n## Questions and next steps\n\n## Why I'm interested", | ||
'support': "## Details about the problem\n\n## A photo or screenshot of the setup", | ||
'event': "## Event details\n\nWhen, where, what\n\n## Background\n\nWho, why", | ||
'question': "## What I want to do or know\n\n## Background story" | ||
}, | ||
previewing: false, | ||
previewed: false, | ||
generate_preview: function(id,text) { | ||
$('#'+id)[0].innerHTML = marked(text) | ||
}, | ||
toggle_preview: function() { | ||
let previewBtn; | ||
let dropzone; | ||
} | ||
generate_preview(id,text) { | ||
$('#' + id)[0].innerHTML = marked(text) | ||
} | ||
toggle_preview() { | ||
// if the element is part of a multi-comment page, | ||
// ensure to grab the current element and not the other comment element. | ||
previewBtn = $(this.textarea.context).find('.preview-btn'); | ||
dropzone = $(this.textarea.context).find('.dropzone'); | ||
const previewBtn = $("#toggle-preview-button-" + this.commentFormID); | ||
const dropzone = $("#dropzone-large-" + this.commentFormID); | ||
|
||
$E.preview[0].innerHTML = marked($E.textarea.val()); | ||
$E.preview.toggle(); | ||
this.previewElement[0].innerHTML = marked(this.textAreaValue); | ||
this.previewElement.toggle(); | ||
dropzone.toggle(); | ||
|
||
this.toggleButtonPreviewMode(previewBtn); | ||
}, | ||
toggleButtonPreviewMode: function (previewBtn) { | ||
} | ||
toggleButtonPreviewMode(previewBtn) { | ||
let isPreviewing = previewBtn.attr('data-previewing'); | ||
|
||
// If data-previewing attribute is not present -> we are not in "preview" mode | ||
|
@@ -166,4 +160,4 @@ $E = { | |
previewBtn.text('Preview'); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing the default parameter (
commentFormID = "main"
) here. I think it makes sense so that errors will be easier to catch.