Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# angular-pagedown

A pagedown editor for AngularJS. View this [Plunker](http://plnkr.co/edit/2LZiw454g77k6aE3HTyd) for demo.
Read this complete guide to how to implement it on [Medium](https://medium.com/@hmodi2457/page-down-editor-with-angular-js-b5021268b8b7) with images.

## Instructions

Expand Down
263 changes: 125 additions & 138 deletions angular-pagedown.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,166 +6,153 @@ var mdExtraOptions = {

// adapted from http://stackoverflow.com/a/20957476/940030
angular.module("ui.pagedown", [])
.directive("pagedownEditor", ['$compile', '$timeout', '$window', '$q', function ($compile, $timeout, $window, $q) {
var nextId = 0;
var converter = Markdown.getSanitizingConverter();
Markdown.Extra.init(converter, mdExtraOptions);

converter.hooks.chain("preBlockGamut", function (text, rbg) {
return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) {
return "<blockquote>" + rbg(inner) + "</blockquote>\n";
});
.directive("pagedownEditor", ['$compile', '$timeout', '$window', '$q', function ($compile, $timeout, $window, $q) {
var nextId = 0;
var converter = Markdown.getSanitizingConverter();
Markdown.Extra.init(converter, mdExtraOptions);

converter.hooks.chain("preBlockGamut", function (text, rbg) {
return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) {
return "<blockquote>" + rbg(inner) + "</blockquote>\n";
});
});

return {
restrict: "E",
scope: {
content: "=",
placeholder: "@",
showPreview: "@",
help: "&",
insertImage: "&",
editorClass: "=",
editorRows: "@",
previewClass: "=",
previewContent: "="
},
link: function (scope, element, attrs) {

var editorUniqueId;

if (attrs.id === null) {
editorUniqueId = nextId++;
} else {
editorUniqueId = attrs.id;
}

return {
restrict: "E",
require: 'ngModel',
scope: {
ngModel: "=",
placeholder: "@",
showPreview: "@",
help: "&",
insertImage: "&",
editorClass: "=?",
editorRows: "@",
previewClass: "=?",
previewContent: "=?"
},
link: function (scope, element, attrs, ngModel) {

scope.changed = function () {
ngModel.$setDirty();
scope.$parent.$eval(attrs.ngChange);
};


var editorUniqueId;

if (attrs.id == null) {
editorUniqueId = nextId++;
} else {
editorUniqueId = attrs.id;
}

// just hide the preview, we still need it for "onPreviewRefresh" hook
var previewHiddenStyle = scope.showPreview == "false" ? "display: none;" : "";
// just hide the preview, we still need it for "onPreviewRefresh" hook
var previewHiddenStyle = scope.showPreview == "false" ? "display: none;" : "";

var placeholder = attrs.placeholder || "";
var editorRows = attrs.editorRows || "10";
var placeholder = attrs.placeholder || "";
var editorRows = attrs.editorRows || "10";

var newElement = $compile(
'<div>' +
var newElement = $compile(
'<div>' +
'<div class="wmd-panel">' +
'<div id="wmd-button-bar-' + editorUniqueId + '"></div>' +
'<textarea id="wmd-input-' + editorUniqueId + '" placeholder="' + placeholder + '" ng-model="ngModel"' +
' ng-change="changed()"' +
' rows="' + editorRows + '" ' + (scope.editorClass ? 'ng-class="editorClass"' : 'class="wmd-input"') + '>' +
'</textarea>' +
'<div id="wmd-button-bar-' + editorUniqueId + '"></div>' +
'<textarea id="wmd-input-' + editorUniqueId + '" placeholder="' + placeholder + '" ng-model="content"' +
' rows="' + editorRows + '" ' + (scope.editorClass ? 'ng-class="editorClass"' : 'class="wmd-input"') + '>' +
'</textarea>' +
'</div>' +
'<div id="wmd-preview-' + editorUniqueId + '" style="' + previewHiddenStyle + '"' +
' ' + (scope.previewClass ? 'ng-class="previewClass"' : 'class="wmd-panel wmd-preview"') + '>' +
'</div>' +
'</div>')(scope);

// html() doesn't work
element.append(newElement);

var help = angular.isFunction(scope.help) ? scope.help : function () {
// redirect to the guide by default
$window.open("http://daringfireball.net/projects/markdown/syntax", "_blank");
};

var editor = new Markdown.Editor(converter, "-" + editorUniqueId, {
handler: help
});

'</div>')(scope);

var editorElement = angular.element(document.getElementById("wmd-input-" + editorUniqueId));
// html() doesn't work
element.append(newElement);

editorElement.val(scope.ngModel);
var help = angular.isFunction(scope.help) ? scope.help : function () {
// redirect to the guide by default
$window.open("http://daringfireball.net/projects/markdown/syntax", "_blank");
};

converter.hooks.chain("postConversion", function (text) {
ngModel.$setViewValue(editorElement.val());
var editor = new Markdown.Editor(converter, "-" + editorUniqueId, {
handler: help
});

if (scope.previewContent) {
converter.hooks.chain("postConversion", function(text) {
// update
scope.previewContent = text;
return text;
});
}

var editorElement = angular.element(document.getElementById("wmd-input-" + editorUniqueId));

// add watch for content
if (scope.showPreview != "false") {
scope.$watch('content', function () {
editor.refreshPreview();
});
}
editor.hooks.chain("onPreviewRefresh", function () {
// wire up changes caused by user interaction with the pagedown controls
// and do within $apply
$timeout(function () {
scope.content = editorElement.val();
});
// add watch for content
if(scope.showPreview != "false") {
scope.$watch('content', function () {
editor.refreshPreview();
});
}
editor.hooks.chain("onPreviewRefresh", function() {
// wire up changes caused by user interaction with the pagedown controls
// and do within $apply
$timeout(function() {
scope.content = editorElement.val();
});
});

if (angular.isFunction(scope.insertImage)) {
editor.hooks.set("insertImageDialog", function (callback) {
// expect it to return a promise or a url string
var result = scope.insertImage();

// Note that you cannot call the callback directly from the hook; you have to wait for the current scope to be exited.
// https://code.google.com/p/pagedown/wiki/PageDown#insertImageDialog
$timeout(function () {
if (!result) {
// must be null to indicate failure
callback(null);
} else {
// safe way to handle either string or promise
$q.when(result).then(
function success(imgUrl) {
callback(imgUrl);
},
function error(reason) {
callback(null);
}
);
}
});

return true;
if (angular.isFunction(scope.insertImage)) {
editor.hooks.set("insertImageDialog", function(callback) {
// expect it to return a promise or a url string
var result = scope.insertImage();

// Note that you cannot call the callback directly from the hook; you have to wait for the current scope to be exited.
// https://code.google.com/p/pagedown/wiki/PageDown#insertImageDialog
$timeout(function() {
if (!result) {
// must be null to indicate failure
callback(null);
} else {
// safe way to handle either string or promise
$q.when(result).then(
function success(imgUrl) {
callback(imgUrl);
},
function error(reason) {
callback(null);
}
);
}
});
}

editor.run();
return true;
});
}

editor.run();
}
}])
.directive("pagedownViewer", ['$compile', '$sce', function ($compile, $sce) {
var converter = Markdown.getSanitizingConverter();
Markdown.Extra.init(converter, mdExtraOptions);

return {
restrict: "E",
scope: {
content: "="
},
link: function (scope, element, attrs) {
var run = function run() {
if (!scope.content) {
scope.sanitizedContent = '';
return;
}

scope.sanitizedContent = $sce.trustAsHtml(converter.makeHtml(scope.content));
};

scope.$watch("content", run);

run();

var newElementHtml = "<p ng-bind-html='sanitizedContent'></p>";
var newElement = $compile(newElementHtml)(scope);

element.append(newElement);
}
}
}])
.directive("pagedownViewer", ['$compile', '$sce', function ($compile, $sce) {
var converter = Markdown.getSanitizingConverter();
Markdown.Extra.init(converter, mdExtraOptions);

return {
restrict: "E",
scope: {
content: "="
},
link: function (scope, element, attrs) {
var run = function run() {
if (!scope.content) {
scope.sanitizedContent = '';
return;
}

scope.sanitizedContent = $sce.trustAsHtml(converter.makeHtml(scope.content));
};

scope.$watch("content", run);

run();

var newElementHtml = "<p ng-bind-html='sanitizedContent'></p>";
var newElement = $compile(newElementHtml)(scope);

element.append(newElement);
}
}]);
}
}]);