diff --git a/ccm-bundle-devel-wildfly-web/pom.xml b/ccm-bundle-devel-wildfly-web/pom.xml
index 494d2f190..1096d4966 100644
--- a/ccm-bundle-devel-wildfly-web/pom.xml
+++ b/ccm-bundle-devel-wildfly-web/pom.xml
@@ -90,6 +90,11 @@
maven-war-plugin
+
+ org.libreccm
+ ccm-editor
+ jar
+
org.libreccm
ccm-theme-foundry
diff --git a/ccm-bundle-devel-wildfly-web/src/main/webapp/ccm-editor/ccm-editor-loader.js b/ccm-bundle-devel-wildfly-web/src/main/webapp/ccm-editor/ccm-editor-loader.js
new file mode 100644
index 000000000..ba5deb406
--- /dev/null
+++ b/ccm-bundle-devel-wildfly-web/src/main/webapp/ccm-editor/ccm-editor-loader.js
@@ -0,0 +1,67 @@
+requirejs(["./ccm-editor",
+ "../node_modules/requirejs-domready/domReady!"],
+ function(editor, doc) {
+
+ editor.addEditor(".editor", {
+ "actionGroups": [
+ {
+ "name": "blocks",
+ "title": "Format blocks",
+ "actions": [
+ editor.FormatBlockAction
+ ]
+ },
+ {
+ "name": "format-text",
+ "title": "Format text",
+ "actions": [
+ editor.MakeBoldAction,
+ editor.MakeItalicAction,
+ editor.MakeUnderlineAction,
+ editor.StrikeThroughAction,
+ editor.SubscriptAction,
+ editor.SuperscriptAction,
+ editor.RemoveFormatAction,
+ editor.InsertExternalLinkAction
+ ]
+ },
+ {
+ "name": "insert-list",
+ "title": "Insert list",
+ "actions": [
+ editor.InsertUnorderedListAction,
+ editor.InsertOrderedListAction
+ ]
+ },
+ {
+ "name": "html",
+ "title": "HTML",
+ "actions": [editor.ToggleHtmlAction]
+ }
+ ],
+ "settings": {
+ "formatBlock.blocks": [
+ {
+ "element": "h3",
+ "title": "Heading 3"
+ },
+ {
+ "element": "h4",
+ "title": "Heading 4"
+ },
+ {
+ "element": "h5",
+ "title": "Heading 5"
+ },
+ {
+ "element": "h6",
+ "title": "Heading 6"
+ },
+ {
+ "element": "p",
+ "title": "Paragraph"
+ }
+ ]
+ }
+ });
+});
diff --git a/ccm-bundle-devel/pom.xml b/ccm-bundle-devel/pom.xml
index 4e4899d2d..6687c1663 100644
--- a/ccm-bundle-devel/pom.xml
+++ b/ccm-bundle-devel/pom.xml
@@ -26,6 +26,11 @@
${project.parent.version}
+
+ org.libreccm
+ ccm-editor
+ ${project.parent.version}
+
org.libreccm
ccm-theme-foundry
diff --git a/ccm-core/src/main/java/org/libreccm/security/PermissionMarshaller.java b/ccm-core/src/main/java/org/libreccm/security/PermissionMarshaller.java
index a2ca20192..6b2dd50ed 100644
--- a/ccm-core/src/main/java/org/libreccm/security/PermissionMarshaller.java
+++ b/ccm-core/src/main/java/org/libreccm/security/PermissionMarshaller.java
@@ -27,7 +27,7 @@ import javax.persistence.EntityManager;
import javax.transaction.Transactional;
/**
- * @author Tobias Osmers
* @version created on 11/7/16
*/
@RequestScoped
diff --git a/ccm-core/src/main/java/org/libreccm/security/RoleMembershipMarshaller.java b/ccm-core/src/main/java/org/libreccm/security/RoleMembershipMarshaller.java
index 184f40b4e..c20c9f531 100644
--- a/ccm-core/src/main/java/org/libreccm/security/RoleMembershipMarshaller.java
+++ b/ccm-core/src/main/java/org/libreccm/security/RoleMembershipMarshaller.java
@@ -29,7 +29,7 @@ import javax.persistence.EntityManager;
import javax.transaction.Transactional;
/**
- * @author Tobias Osmers
* @version created on 11/7/16
*/
@RequestScoped
diff --git a/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java b/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java
index 147804fc8..22c6ecf90 100644
--- a/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java
+++ b/ccm-core/src/test/java/org/libreccm/portation/ImportHelper.java
@@ -67,8 +67,8 @@ import javax.inject.Inject;
@RequestScoped
class ImportHelper {
- //private String repoPath = "/home/jensp/pwi/libreccm/ccm/";
- private final String repoPath = "/home/tosmers/Svn/libreccm/";
+ private final String repoPath = "/home/jensp/pwi/libreccm/ccm/";
+// private final String repoPath = "/home/tosmers/Svn/libreccm/";
private final String projectPath = "ccm_ng/ccm-core/src/test/resources/" +
"portation/trunk-iaw-exports";
private final boolean indentation = false;
diff --git a/ccm-editor/Gruntfile.js b/ccm-editor/Gruntfile.js
new file mode 100644
index 000000000..b78967ff4
--- /dev/null
+++ b/ccm-editor/Gruntfile.js
@@ -0,0 +1,17 @@
+module.exports = function(grunt) {
+ grunt.initConfig({
+ ts: {
+ default : {
+ src: ["src/main/typescript/**/*.ts"],
+ options: {
+ module: "amd"
+ }
+ }
+ },
+ clean: ['scripts/*.js', 'scripts/*.js.map', 'scripts/.tscache']
+ });
+ grunt.loadNpmTasks("grunt-ts");
+ grunt.loadNpmTasks('grunt-contrib-clean');
+ grunt.registerTask("default", ["ts"]);
+};
+
diff --git a/ccm-editor/package.json b/ccm-editor/package.json
new file mode 100644
index 000000000..9f940a7ca
--- /dev/null
+++ b/ccm-editor/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "ccm-editor",
+ "version": "7.0.0",
+ "description": "Simple HTML editor for LibreCCM",
+ "repository": {
+ "type": "svn",
+ "url": "https://svn.libreccm.org/ccm/ccm_ng/ccm-editor"
+ },
+ "author": "Jens Pelzetter",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "font-awesome": "^4.7.0",
+ "fontawesome": "^4.7.2",
+ "grunt": "^1.0.1",
+ "grunt-cli": "^1.2.0",
+ "grunt-contrib-clean": "^1.1.0",
+ "grunt-ts": "^6.0.0-beta.17",
+ "requirejs": "^2.3.5",
+ "requirejs-domready": "^2.0.3",
+ "typescript": "^2.6.2"
+ },
+ "devDependencies": {}
+}
diff --git a/ccm-editor/pom.xml b/ccm-editor/pom.xml
new file mode 100644
index 000000000..dbd415007
--- /dev/null
+++ b/ccm-editor/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+ 4.0.0
+
+
+ UTF-8
+ ${maven.build.timestamp}
+ yyyy-MM-dd'T'HH:mm:ss'Z'Z
+
+
+
+ org.libreccm
+ libreccm-parent
+ 7.0.0-SNAPSHOT
+
+
+ org.libreccm
+ ccm-editor
+ 7.0.0-SNAPSHOT
+ jar
+
+ LibreCCM Editor
+
+
+
+ Lesser GPL 2.1
+ http://www.gnu.org/licenses/old-licenses/lgpl-2.1
+
+
+
+
+ ccm-editor
+
+
+
+ src/main/resources
+
+
+ src/main/typescript
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+
+
+ Install node.js and NPM
+
+ install-node-and-npm
+
+
+ v6.12.3
+
+
+
+ npm install
+
+ npm
+
+
+
+ grunt build
+
+ grunt
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ccm-editor/src/main/resources/ccm-editor/ccm-editor.css b/ccm-editor/src/main/resources/ccm-editor/ccm-editor.css
new file mode 100644
index 000000000..bc60f4b70
--- /dev/null
+++ b/ccm-editor/src/main/resources/ccm-editor/ccm-editor.css
@@ -0,0 +1,102 @@
+.ccm-editor {
+ box-sizing: border-box;
+
+ width: 60em;
+}
+
+.ccm-editor-accessibility {
+ box-sizing: inherit;
+
+ position: absolute;
+ display: block;
+
+ width: 1px;
+ height: 1px;
+
+ overflow: hidden;
+}
+
+.ccm-editor-dialog {
+
+ background-color: #ccc;
+
+ border: 1px solid #aaa;
+
+ box-sizing: border-box;
+
+ padding: 1em 0.75em;
+
+ position: absolute;
+ left: 50%;
+ top: 50%;
+
+ margin-top: -10em;
+ margin-left: -20em;
+
+ width: 40em;
+ /*height: 20em;*/
+}
+
+.ccm-editor-dialog h1 {
+ margin-top: 0;
+}
+
+.ccm-editor-dialog form {
+ display: grid;
+ grid-template-columns: 1fr 2fr;
+}
+
+.ccm-editor-dialog form input[type="checkbox"] {
+ justify-self: start;
+}
+
+.ccm-editor-dialog form button {
+ justify-self: center;
+}
+
+.ccm-editor-editable {
+
+ background-color: #fff;
+ color: #000;
+
+ border: 1px solid #bbb;
+
+ box-sizing: inherit;
+
+ height: 20em;
+
+ padding: 0.5em 0.3em;
+}
+
+.ccm-editor-hidden {
+ display: none;
+}
+
+.ccm-editor-textarea {
+
+ background-color: #fff;
+ color: #000;
+
+ box-sizing: inherit;
+
+ width: 60em;
+}
+
+.ccm-editor-toolbar {
+ box-sizing: inherit;
+
+ background-color: #ccc;
+
+ border: 1px solid #ccc;
+
+ padding: 0.2em 0.33em;
+}
+
+.ccm-editor-toolbar-actiongroup {
+ box-sizing: inherit;
+
+ display: inline-block;
+
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+}
diff --git a/ccm-editor/src/main/typescript/ccm-editor/ccm-editor.ts b/ccm-editor/src/main/typescript/ccm-editor/ccm-editor.ts
new file mode 100644
index 000000000..cc2b63d10
--- /dev/null
+++ b/ccm-editor/src/main/typescript/ccm-editor/ccm-editor.ts
@@ -0,0 +1,1029 @@
+/**
+ * Them main class for the editor.
+ */
+export class CCMEditor {
+
+ private configuration: CCMEditorConfiguration;
+ private textarea: HTMLTextAreaElement;
+ private editorDiv: Element;
+ private editorToolbar: Element;
+ private htmlSourceToolbar: Element;
+ private actions: CCMEditorAction[] = [];
+
+ constructor(textarea: HTMLTextAreaElement,
+ configuration?: CCMEditorConfiguration) {
+
+ if (textarea == undefined || textarea == null) {
+ throw new Error("No TextArea provided.");
+ }
+
+ if (configuration == undefined) {
+
+ console.log("No configuration provided, using default configuration.");
+
+ this.configuration = {
+ "actionGroups": [
+ {
+ "name": "mark-text",
+ "title": "Mark text",
+ "actions": [MakeBoldAction, MakeItalicAction]
+ }
+ ],
+ "settings":
+ {
+ "formatBlock.blocks": [
+ {
+ "element": "h3",
+ "title": "Heading 3"
+ },
+ {
+ "element": "h4",
+ "title": "Heading 4"
+ },
+ {
+ "element": "h5",
+ "title": "Heading 5"
+ },
+ {
+ "element": "h6",
+ "title": "Heading 6"
+ },
+ {
+ "element": "p",
+ "title": "Paragraph"
+ },
+ {
+ "element": "blockquote",
+ "title": "Blockquote"
+ }
+ ]
+ }
+ };
+ } else {
+ this.configuration = configuration;
+ }
+
+ if (textarea.tagName.toLowerCase() != "textarea") {
+ throw new Error("Provided element is not a textarea");
+ }
+
+ this.textarea = textarea;
+
+ console.log("Adding actions...")
+ for(const actionGroupKey in this.configuration.actionGroups) {
+
+ const actionGroup: CCMEditorActionGroup = this
+ .configuration
+ .actionGroups[actionGroupKey];
+ for(const actionKey in actionGroup.actions) {
+
+ console.log("Adding action " + actionKey);
+ const actionClass = actionGroup.actions[actionKey];
+ const actionObj: CCMEditorAction
+ = new actionClass(this, configuration.settings);
+ this.actions[actionGroup.actions[actionKey]] = actionObj;
+ }
+ }
+
+ const editor: Element = document.createElement("div");
+ editor.classList.add("ccm-editor");
+
+ this.editorToolbar = editor.appendChild(document.createElement("div"));
+ this.editorToolbar.classList.add("ccm-editor-toolbar");
+
+ for(const actionGroupKey in this.configuration.actionGroups) {
+
+ const actionGroup: CCMEditorActionGroup = this
+ .configuration
+ .actionGroups[actionGroupKey];
+ const actionGroupElem: Element = this.editorToolbar
+ .appendChild(document.createElement("div"));
+ actionGroupElem.classList.add("ccm-editor-toolbar-actiongroup");
+
+ for(const actionKey in actionGroup.actions) {
+
+ const actionObj: CCMEditorAction = this
+ .actions[actionGroup.actions[actionKey]];
+ actionGroupElem.appendChild(actionObj.getDocumentFragment());
+ }
+ }
+
+ this.editorDiv = editor.appendChild(document.createElement("div"));
+ this.editorDiv.classList.add("ccm-editor-editable");
+ this.editorDiv.setAttribute("contenteditable", "true");
+ this.editorDiv.innerHTML = this.textarea.value.trim();
+ this.editorDiv.addEventListener("input", event => this.syncTextArea());
+ document.addEventListener("selectionchange",
+ event => this.selectionChanged());
+
+ this.htmlSourceToolbar = editor
+ .appendChild(document.createElement("div"));
+ this.htmlSourceToolbar.classList.add("ccm-editor-toolbar");
+ this.htmlSourceToolbar.classList.add("ccm-editor-hidden");
+ const showEditorButton: Element = this.htmlSourceToolbar
+ .appendChild(document.createElement("button"));
+ const showEditorIcon: Element = showEditorButton
+ .appendChild(document.createElement("i"));
+ showEditorIcon.className = "fa fa-edit";
+ const showEditorText: Element = showEditorButton
+ .appendChild(document.createElement("span"));
+ showEditorButton.setAttribute("title", "Show editor");
+ showEditorText.textContent = "Show editor";
+ showEditorText.className = "ccm-editor-accessibility";
+ const ccmeditor: CCMEditor = this;
+ showEditorButton.addEventListener("click", function() {
+ ccmeditor.toggleHtml();
+ });
+
+ this.textarea.addEventListener("input", event => this.syncEditor());
+
+ const textareaParent: Node = this.textarea.parentNode;
+ textareaParent.insertBefore(editor, textarea);
+ this.textarea.classList.add("ccm-editor-textarea");
+ this.textarea.classList.add("ccm-editor-hidden");
+
+ const headElem: Element = document.getElementsByTagName("head").item(0);
+ const styleElem: Element = document.createElement("link");
+ styleElem.setAttribute("rel", "stylesheet");
+ styleElem.setAttribute("href", "libreccm-editor.css");
+ headElem.appendChild(styleElem);
+
+ //Check if Fontawesome is already loaded. If not add it
+ if (!this.isFontAwesomeLoaded()) {
+
+ const fontawesomeElem: Element = document.createElement("link");
+ fontawesomeElem.setAttribute("rel", "stylesheet");
+ fontawesomeElem.setAttribute("href", "node_modules/font-awesome/css/font-awesome.min.css");
+ headElem.appendChild(fontawesomeElem);
+ }
+
+ //Avoid
+ document.execCommand("insertBrOnReturn", false, false);
+ }
+
+ public toggleHtml(): void {
+ console.log("Toggle HTML view...");
+ if (this.textarea.classList.contains("ccm-editor-hidden")) {
+ console.log("HTML not visible, making visible...");
+ this.textarea.classList.remove("ccm-editor-hidden");
+ this.editorDiv.classList.add("ccm-editor-hidden");
+ } else {
+ console.log("HTML is visible, make invisible...");
+ this.textarea.classList.add("ccm-editor-hidden");
+ this.editorDiv.classList.remove("ccm-editor-hidden");
+ }
+ }
+
+ public syncTextArea() {
+ this.textarea.value = this.editorDiv.innerHTML;
+ }
+
+ public syncEditor() {
+ this.editorDiv.innerHTML = this.textarea.value.trim();
+ }
+
+ private selectionChanged() {
+ console.log("Selection changed.");
+ const selection: Selection = document.getSelection();
+ for(const key in this.actions) {
+
+ this.actions[key].selectionChanged(selection);
+ }
+ }
+
+ private isFontAwesomeLoaded(): boolean {
+
+ const linkElems: NodeList = document.getElementsByTagName("link");
+
+ for(let i = 0; i < linkElems.length; i++) {
+
+ const linkElem: Element = linkElems.item(i) as Element;
+
+ if (linkElem.hasAttribute("rel")
+ && linkElem.getAttribute("rel") == "stylesheet"
+ && linkElem.getAttribute("href").indexOf("fontawesome") != -1) {
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+export function addEditor(selector: string,
+ configuration?: CCMEditorConfiguration) {
+
+ const selectedElements: NodeList = document.querySelectorAll(selector);
+
+ for(let i = 0; i < selectedElements.length; i++) {
+
+ const selectedElement: HTMLTextAreaElement
+ = selectedElements.item(i) as HTMLTextAreaElement;
+ new CCMEditor(selectedElement, configuration);
+ }
+}
+
+interface CCMEditorConfiguration {
+
+ actionGroups: CCMEditorActionGroup[]
+
+ settings: {}
+}
+
+interface CCMEditorActionGroup {
+
+ name: string;
+ title: string;
+
+ actions: any[];
+}
+
+/**
+ * Possible types for actions.
+ */
+export enum CCMEditorActionType {
+
+ INSERT_BLOCK,
+ INSERT_INLINE,
+ OTHER
+}
+
+/**
+ * Base class for all editor actions.
+ */
+export abstract class CCMEditorAction {
+
+ protected editor: CCMEditor;
+ protected settings: string[];
+
+ protected fragment: DocumentFragment;
+
+ constructor(editor: CCMEditor, settings: any) {
+ this.editor = editor;
+ this.settings = settings;
+
+ this.fragment = document.createDocumentFragment();
+ }
+
+ /**
+ * Generate the HTML for the control(s) for the action. The Element
+ * returned by this method is added to the toolbar of the editor if
+ * the plugin is added to the editor.
+ */
+ getDocumentFragment(): DocumentFragment {
+ return this.fragment;
+ }
+
+ /**
+ * Return the type of the action.
+ */
+ abstract getActionType(): CCMEditorActionType;
+
+ /**
+ * Verify is the action is applicable for the current element.
+ * This will be a future extension.
+ *
+ * @param element The element.
+ */
+ public isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ public abstract selectionChanged(selection: Selection): void;
+
+ /**
+ * Enables the controls for the action.
+ */
+ abstract enableAction(): void;
+
+ /**
+ * Disables the controls for the action.
+ */
+ abstract disableAction(): void;
+}
+
+export class FormatBlockAction extends CCMEditorAction {
+
+ private blockSelect: HTMLSelectElement;
+ private values: string[];
+
+ constructor(editor: CCMEditor, settings: any) {
+ super(editor, settings);
+
+ const blockSelectLabel = this.fragment
+ .appendChild(document.createElement("label"));
+ const blockSelectLabelSpan = blockSelectLabel
+ .appendChild(document.createElement("span"));
+ blockSelectLabelSpan.textContent = "Block element";
+ blockSelectLabelSpan.className = "ccm-editor-accessibility";
+ this.blockSelect = blockSelectLabel
+ .appendChild(document.createElement("select"));
+
+ const emptyOption: Element = document.createElement("option");
+ emptyOption.setAttribute("value", "");
+ emptyOption.textContent = "";
+ this.blockSelect.appendChild(emptyOption);
+
+ this.values = [];
+
+ for(const block of settings["formatBlock.blocks"]) {
+ const option: Element = document.createElement("option");
+ option.setAttribute("value", block.element.toLowerCase());
+ this.values.push(block.element);
+ option.textContent = block.title;
+ this.blockSelect.appendChild(option);
+ }
+
+ const blockSelect: HTMLSelectElement = this.blockSelect;
+ this.blockSelect.addEventListener("change", function(event) {
+
+ const blockElem: string = blockSelect.value;
+ if (blockElem !== null && blockElem.length > 0) {
+ document.execCommand("formatBlock", false, blockElem);
+ editor.syncTextArea();
+ }
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_BLOCK;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ console.log("selection.anchorNode = " + selection.anchorNode);
+ const blockElem: Element = this.findBlockElement(selection.anchorNode);
+ if (blockElem === null) {
+ console.log("Selection is not in a known block element.");
+ this.blockSelect.value = "";
+ } else {
+ console.log("Selection is in block element \"" + blockElem.tagName.toLowerCase + "\"");
+ this.blockSelect.value = blockElem.tagName.toLowerCase();
+ }
+ }
+
+ private findBlockElement(node: Node): Element {
+
+ if (node instanceof Element) {
+ console.log("Current node is an element.");
+ const elem: Element = node as Element;
+ console.log("elem.tagName = " + elem.tagName.toLowerCase());
+ if(this.values.indexOf(elem.tagName.toLowerCase()) === -1) {
+ console.log("elem.tagName is not in the values array.");
+ if (elem.parentNode === null) {
+ console.log("elem has no parent node. Returning null.");
+ return null;
+ } else {
+ console.log("Continuing with elem.parentNode");
+ return this.findBlockElement(elem.parentNode);
+ }
+ } else {
+ return elem;
+ }
+ } else {
+ console.log("Current node is not an element node.");
+ if (node.parentNode === null) {
+ console.log("Current node has no parent, returning null.");
+ return null;
+ } else {
+ console.log("Continuing with parent node...");
+ return this.findBlockElement(node.parentNode);
+ }
+ }
+ }
+
+ enableAction(): void {
+ this.blockSelect.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.blockSelect.setAttribute("disabled", "true");
+ }
+}
+
+/**
+ * Action for making the selected text bold (strongly emphasised). Wraps the
+ * selected text in a b element.
+ */
+export class MakeBoldAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-bold";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button
+ .setAttribute("title", "Make selected text bold (mark as strongly emphasised).");
+ text.textContent = "Bold";
+ //text.textContent = "Make selected text bold (mark as strongly emphasised).";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event) {
+
+ event.preventDefault();
+ document.execCommand("bold");
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_INLINE;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+
+}
+
+/**
+ * Action for making the selected text italic (emphasised). Wraps the
+ * selected text in a i element.
+ */
+export class MakeItalicAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-italic";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title", "Make selected italic (mark as emphasised)");
+ //text.textContent = "Make selected italic (mark as emphasised).";
+ text.textContent = "Italic";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event) {
+
+ event.preventDefault();
+ document.execCommand("italic");
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_INLINE;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+/**
+ * Action for making the selected text underlined. Wraps the selected text
+ * in an u element
+ */
+export class MakeUnderlineAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-underline";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title",
+ "Make selected text underlined. "
+ + "Use with caution because many people "
+ + "think that underlined text is a link.")
+ text.textContent = "Underline";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event) {
+
+ event.preventDefault();
+ document.execCommand("underline");
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_INLINE;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+export class StrikeThroughAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-strikethrough";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title", "Strike through selected text.");
+ text.textContent = "Strike out";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event){
+
+ event.preventDefault();
+ document.execCommand("strikeThrough");
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_INLINE;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+export class ToggleHtmlAction extends CCMEditorAction {
+
+ private showHtmlButton: HTMLButtonElement;
+
+ constructor(editor: CCMEditor, settings: any) {
+ super(editor, settings);
+
+ this.showHtmlButton = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.showHtmlButton
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-code";
+
+ const text: Element = this.showHtmlButton
+ .appendChild(document.createElement("span"));
+ this.showHtmlButton.setAttribute("title", "Show HTML source code.");
+ text.textContent = "Show HTML";
+ text.className = "ccm-editor-accessibility";
+
+ this.showHtmlButton.addEventListener("click", function(event) {
+
+ event.preventDefault();
+ editor.toggleHtml();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.OTHER;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.showHtmlButton.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.showHtmlButton.setAttribute("disabled", "true");
+ }
+}
+
+export class SubscriptAction extends CCMEditorAction {
+
+ private button: HTMLButtonElement;
+
+ constructor(editor: CCMEditor, settings: any) {
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-subscript";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title", "Make subscript.");
+ text.textContent = "Subscript";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event) {
+
+ event.preventDefault();
+ document.execCommand("subscript");
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_INLINE;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+export class SuperscriptAction extends CCMEditorAction {
+
+ private button: HTMLButtonElement;
+
+ constructor(editor: CCMEditor, settings: any) {
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-superscript";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title", "Make superscript.");
+ text.textContent = "Superscript";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event) {
+
+ event.preventDefault();
+ document.execCommand("superscript");
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_INLINE;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+export class RemoveFormatAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-remove";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title", "Remove all formatting from selected text.");
+ text.textContent = "Remove format";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event){
+
+ event.preventDefault();
+ document.execCommand("removeFormat");
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.OTHER;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+export class InsertOrderedListAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-list-ol";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title", "Insert an ordered list.");
+ text.textContent = "Ordered list";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event){
+
+ event.preventDefault();
+ document.execCommand("insertOrderedList", false);
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_BLOCK;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+export class InsertUnorderedListAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-list-ul";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title", "Insert an unordered list.");
+ text.textContent = "Unordered list";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event){
+
+ event.preventDefault();
+ document.execCommand("insertUnorderedList", false);
+ editor.syncTextArea();
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_BLOCK;
+ }
+
+ isApplicableFor(element: Element): Boolean {
+ return true;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
+
+export class InsertExternalLinkAction extends CCMEditorAction {
+
+ private button: Element;
+
+ constructor(editor: CCMEditor, settings: any) {
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-external-link";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button
+ .setAttribute("title", "Insert an external link.");
+ text.textContent = "External link";
+ text.className = "ccm-editor-accessibility";
+
+ this.button.addEventListener("click", function(event) {
+
+ event.preventDefault();
+
+ const currentRange: Range = document.getSelection().getRangeAt(0);
+
+ const dialogFragment: DocumentFragment = document
+ .createDocumentFragment();
+ const dialogElem: Element = dialogFragment
+ .appendChild(document.createElement("div"));
+ dialogElem.className = "ccm-editor-dialog";
+ const dialogTitleElem: Element = dialogElem
+ .appendChild(document.createElement("h1"));
+ dialogTitleElem.textContent = "Create external link";
+ const dialogForm: HTMLFormElement = dialogElem
+ .appendChild(document.createElement("form"));
+ const urlFieldLabel: Element = dialogForm
+ .appendChild(document.createElement("label"));
+ urlFieldLabel.setAttribute("for", "ccm-editor-external-link-url");
+ urlFieldLabel.textContent = "Target URL";
+ const urlField: HTMLInputElement = dialogForm
+ .appendChild(document.createElement("input"));
+ urlField.setAttribute("id", "ccm-editor-external-link-url");
+ urlField.setAttribute("type", "text");
+ const newWindowLabel: Element = dialogForm
+ .appendChild(document.createElement("label"));
+ newWindowLabel.setAttribute("for",
+ "ccm-editor-external-link-new-window");
+ newWindowLabel.textContent = "Open in new Window?";
+ const newWindowCheckbox: HTMLInputElement = dialogForm
+ .appendChild(document.createElement("input"));
+ newWindowCheckbox.setAttribute("id",
+ "ccm-editor-external-link-new-window");
+ newWindowCheckbox.setAttribute("type", "checkbox");
+ const okButton: HTMLButtonElement = dialogForm
+ .appendChild(document.createElement("button"));
+ const cancelButton: HTMLButtonElement = dialogForm
+ .appendChild(document.createElement("button"));
+
+ okButton.textContent = "OK";
+ cancelButton.textContent = "Cancel";
+
+ const bodyElem = document.getElementsByTagName("body").item(0);
+
+ okButton.addEventListener("click", function(event){
+ event.preventDefault();
+
+ bodyElem.removeChild(dialogElem);
+ document.getSelection().removeAllRanges();
+ document.getSelection().addRange(currentRange);
+
+ document.execCommand("createLink", false, urlField.value);
+
+ return false;
+ });
+
+ cancelButton.addEventListener("click", function(){
+ event.preventDefault();
+
+ bodyElem.removeChild(dialogElem);
+ document.getSelection().removeAllRanges();
+ document.getSelection().addRange(currentRange);
+
+ return false;
+ });
+
+ bodyElem.appendChild(dialogFragment);
+
+ return false;
+ });
+ }
+
+ getActionType(): CCMEditorActionType {
+ return CCMEditorActionType.INSERT_INLINE;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableAction(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableAction(): void {
+ this.button.setAttribute("disabled", "true");
+ }
+}
diff --git a/pom.xml b/pom.xml
index 57dfe9f4f..091167a42 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,9 @@
ccm-docrepo
ccm-shortcuts
+
+ ccm-editor
+
ccm-theme-foundry
@@ -216,6 +219,11 @@
build-helper-maven-plugin
3.0.0
+
+ com.github.eirslett
+ frontend-maven-plugin
+ 1.6
+
org.codehaus.mojo
findbugs-maven-plugin