Ghost/ghost/admin/mixins/marker-manager.js
David Arvelo 2151d8a49f Reach Editor parity with Ember
closes #2426, closes #2781, closes #2913
- Concatenate vendor files on change of js in core/shared/
- Add all the markerManager stuff to its own mixin
- make markers a shared object for all that mix it in. makes it easier to use helper functions in different modules
- add getMarkdown method, returns object with two keys holding the markdown: one with markers, the other without
- Clear markers when codemirror is destroyed
- make Editor subcomponents communicate through the Editor Controller
- Set Codemirror and html preview shared scrolling
- Set CodeMirror, html preview css scroll class with util
- Create 'scratch' property in Editor controller; prevents a model save wiping image markers due to markdown bindings
- Add editor and html preview actions to handle img upload start/finish
- disable codemirror when an image is being uploaded, enables on success or failure
- Fix editor wordcount when there are 0 words
- Add modal dialog when transitioning out of the editor with an unsaved post
- Add window.onbeforeunload handling with `.unloadDirtyMessage()` on editor controller
- and various other things
2014-06-13 18:12:03 -04:00

220 lines
7.0 KiB
JavaScript

var MarkerManager = Ember.Mixin.create({
imageMarkdownRegex: /^(?:\{<(.*?)>\})?!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim,
markerRegex: /\{<([\w\W]*?)>\}/,
uploadId: 1,
// create an object that will be shared amongst instances.
// makes it easier to use helper functions in different modules
markers: {},
// Add markers to the line if it needs one
initMarkers: function (line) {
var imageMarkdownRegex = this.get('imageMarkdownRegex'),
markerRegex = this.get('markerRegex'),
editor = this.get('codemirror'),
isImage = line.text.match(imageMarkdownRegex),
hasMarker = line.text.match(markerRegex);
if (isImage && !hasMarker) {
this.addMarker(line, editor.getLineNumber(line));
}
},
// Get the markdown with all the markers stripped
getMarkdown: function (value) {
var marker, id,
editor = this.get('codemirror'),
markers = this.get('markers'),
markerRegexForId = this.get('markerRegexForId'),
oldValue = value || editor.getValue(),
newValue = oldValue;
for (id in markers) {
if (markers.hasOwnProperty(id)) {
marker = markers[id];
newValue = newValue.replace(markerRegexForId(id), '');
}
}
return {
withMarkers: oldValue,
withoutMarkers: newValue
};
},
// check the given line to see if it has an image, and if it correctly has a marker
// in the special case of lines which were just pasted in, any markers are removed to prevent duplication
checkLine: function (ln, mode) {
var editor = this.get('codemirror'),
line = editor.getLineHandle(ln),
imageMarkdownRegex = this.get('imageMarkdownRegex'),
markerRegex = this.get('markerRegex'),
isImage = line.text.match(imageMarkdownRegex),
hasMarker;
// We care if it is an image
if (isImage) {
hasMarker = line.text.match(markerRegex);
if (hasMarker && (mode === 'paste' || mode === 'undo')) {
// this could be a duplicate, and won't be a real marker
this.stripMarkerFromLine(line);
}
if (!hasMarker) {
this.addMarker(line, ln);
}
}
// TODO: hasMarker but no image?
},
// Add a marker to the given line
// Params:
// line - CodeMirror LineHandle
// ln - line number
addMarker: function (line, ln) {
var marker,
markers = this.get('markers'),
editor = this.get('codemirror'),
uploadPrefix = 'image_upload',
uploadId = this.get('uploadId'),
magicId = '{<' + uploadId + '>}',
newText = magicId + line.text;
editor.replaceRange(
newText,
{line: ln, ch: 0},
{line: ln, ch: newText.length}
);
marker = editor.markText(
{line: ln, ch: 0},
{line: ln, ch: (magicId.length)},
{collapsed: true}
);
markers[uploadPrefix + '_' + uploadId] = marker;
this.set('uploadId', uploadId += 1);
},
// Check each marker to see if it is still present in the editor and if it still corresponds to image markdown
// If it is no longer a valid image, remove it
checkMarkers: function () {
var id, marker, line,
editor = this.get('codemirror'),
markers = this.get('markers'),
imageMarkdownRegex = this.get('imageMarkdownRegex');
for (id in markers) {
if (markers.hasOwnProperty(id)) {
marker = markers[id];
if (marker.find()) {
line = editor.getLineHandle(marker.find().from.line);
if (!line.text.match(imageMarkdownRegex)) {
this.removeMarker(id, marker, line);
}
} else {
this.removeMarker(id, marker);
}
}
}
},
// this is needed for when we transition out of the editor.
// since the markers object is persistent and shared between classes that
// mix in this mixin, we need to make sure markers don't carry over between edits.
clearMarkers: function () {
var markers = this.get('markers'),
id,
marker;
// can't just `this.set('markers', {})`,
// since it wouldn't apply to this mixin,
// but only to the class that mixed this mixin in
for (id in markers) {
if (markers.hasOwnProperty(id)) {
marker = markers[id];
delete markers[id];
marker.clear();
}
}
},
// Remove a marker
// Will be passed a LineHandle if we already know which line the marker is on
removeMarker: function (id, marker, line) {
var markers = this.get('markers');
delete markers[id];
marker.clear();
if (line) {
this.stripMarkerFromLine(line);
} else {
this.findAndStripMarker(id);
}
},
// Removes the marker on the given line if there is one
stripMarkerFromLine: function (line) {
var ln,
editor = this.get('codemirror'),
markerRegex = /\{<([\w\W]*?)>\}/,
markerText = line.text.match(markerRegex);
ln = editor.getLineNumber(line);
if (markerText) {
editor.replaceRange(
'',
{line: ln, ch: markerText.index},
{line: ln, ch: markerText.index + markerText[0].length}
);
}
},
// the regex
markerRegexForId: function (id) {
id = id.replace('image_upload_', '');
return new RegExp('\\{<' + id + '>\\}', 'gmi');
},
// Find a marker in the editor by id & remove it
// Goes line by line to find the marker by it's text if we've lost track of the TextMarker
findAndStripMarker: function (id) {
var self = this,
editor = this.get('codemirror');
editor.eachLine(function (line) {
var markerText = self.markerRegexForId(id).exec(line.text),
ln;
if (markerText) {
ln = editor.getLineNumber(line);
editor.replaceRange(
'',
{line: ln, ch: markerText.index},
{line: ln, ch: markerText.index + markerText[0].length}
);
}
});
},
// Find the line with the marker which matches
findLine: function (result_id) {
var editor = this.get('codemirror'),
markers = this.get('markers');
// try to find the right line to replace
if (markers.hasOwnProperty(result_id) && markers[result_id].find()) {
return editor.getLineHandle(markers[result_id].find().from.line);
}
return false;
}
});
export default MarkerManager;