2019-01-22 21:09:09 +01:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
2020-02-20 11:29:27 +01:00
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<script>
|
2019-01-22 21:09:09 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* source.html
|
|
|
|
*
|
|
|
|
* Copyright 2013-2014 Web Power, www.webpower.nl
|
|
|
|
* @author Arjan Haverkamp
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Global vars:
|
|
|
|
var tinymce, // Reference to TinyMCE
|
|
|
|
editor, // Reference to TinyMCE editor
|
|
|
|
codemirror, // CodeMirror instance
|
|
|
|
chr = 0, // Unused utf-8 character, placeholder for cursor
|
|
|
|
isMac = /macintosh|mac os/i.test(navigator.userAgent),
|
|
|
|
CMsettings; // CodeMirror settings
|
|
|
|
|
|
|
|
function inArray(key, arr)
|
|
|
|
{
|
|
|
|
"use strict";
|
|
|
|
arr = '|' + arr.join('|') + '|';
|
|
|
|
return arr.indexOf('|'+key+'|') != -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
(function()
|
|
|
|
{// Initialise (before load)
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
tinymce = parent.tinymce;
|
|
|
|
editor = tinymce.activeEditor;
|
2020-02-20 11:29:27 +01:00
|
|
|
var i, userSettings = editor.settings.codemirror ? editor.settings.codemirror : {};
|
|
|
|
CMsettings = {
|
2019-01-22 21:09:09 +01:00
|
|
|
path: userSettings.path || 'codemirror-4.8',
|
2020-02-20 11:29:27 +01:00
|
|
|
indentOnInit: userSettings.indentOnInit || false,
|
|
|
|
disableFilesMerge: userSettings.disableFilesMerge || false,
|
2019-01-22 21:09:09 +01:00
|
|
|
config: {// Default config
|
|
|
|
mode: 'htmlmixed',
|
|
|
|
theme: 'default',
|
|
|
|
lineNumbers: true,
|
|
|
|
lineWrapping: true,
|
|
|
|
indentUnit: 2,
|
|
|
|
tabSize: 2,
|
|
|
|
indentWithTabs: true,
|
|
|
|
matchBrackets: true,
|
2020-02-20 11:29:27 +01:00
|
|
|
saveCursorPosition: true,
|
2020-03-14 10:32:17 +01:00
|
|
|
styleActiveLine: false
|
2019-01-22 21:09:09 +01:00
|
|
|
},
|
|
|
|
jsFiles: [// Default JS files
|
|
|
|
'lib/codemirror.js',
|
|
|
|
'addon/edit/matchbrackets.js',
|
|
|
|
'mode/xml/xml.js',
|
|
|
|
'mode/javascript/javascript.js',
|
|
|
|
'mode/css/css.js',
|
|
|
|
'mode/htmlmixed/htmlmixed.js',
|
|
|
|
'addon/dialog/dialog.js',
|
|
|
|
'addon/search/searchcursor.js',
|
|
|
|
'addon/search/search.js',
|
|
|
|
'addon/selection/active-line.js'
|
|
|
|
],
|
|
|
|
cssFiles: [// Default CSS files
|
|
|
|
'lib/codemirror.css',
|
|
|
|
'addon/dialog/dialog.css'
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
if (userSettings.fullscreen) {
|
|
|
|
CMsettings.jsFiles.push('addon/display/fullscreen.js');
|
|
|
|
CMsettings.cssFiles.push('addon/display/fullscreen.css');
|
|
|
|
}
|
|
|
|
|
2020-02-20 11:29:27 +01:00
|
|
|
if (CMsettings.disableFilesMerge) {
|
|
|
|
if (
|
|
|
|
Array.isArray(userSettings.jsFiles) &&
|
|
|
|
Array.isArray(userSettings.cssFiles) &&
|
|
|
|
(userSettings.jsFiles.length > 0) &&
|
|
|
|
(userSettings.cssFiles.length > 0)
|
|
|
|
) {
|
|
|
|
CMsettings.jsFiles = []
|
|
|
|
CMsettings.cssFiles = []
|
|
|
|
} else {
|
|
|
|
if (console) {
|
|
|
|
console.error('Codemirror plugin: jsFiles and cssFiles must be specified if disableFilesMerge is set to true')
|
|
|
|
console.warn('Codemirror plugin: ignoring disableFilesMerge')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Merge config
|
|
|
|
for (i in userSettings.config) {
|
|
|
|
CMsettings.config[i] = userSettings.config[i];
|
|
|
|
}
|
|
|
|
|
2019-01-22 21:09:09 +01:00
|
|
|
|
|
|
|
// Merge jsFiles
|
|
|
|
for (i in userSettings.jsFiles) {
|
|
|
|
if (!inArray(userSettings.jsFiles[i], CMsettings.jsFiles)) {
|
|
|
|
CMsettings.jsFiles.push(userSettings.jsFiles[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Merge cssFiles
|
|
|
|
for (i in userSettings.cssFiles) {
|
|
|
|
if (!inArray(userSettings.cssFiles[i], CMsettings.cssFiles)) {
|
|
|
|
CMsettings.cssFiles.push(userSettings.cssFiles[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add trailing slash to path
|
|
|
|
if (!/\/$/.test(CMsettings.path)) {
|
|
|
|
CMsettings.path += '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write stylesheets
|
|
|
|
for (i = 0; i < CMsettings.cssFiles.length; i++) {
|
|
|
|
document.write('<li'+'nk rel="stylesheet" type="text/css" href="' + CMsettings.path + CMsettings.cssFiles[i] + '" />');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write JS source files
|
|
|
|
for (i = 0; i < CMsettings.jsFiles.length; i++) {
|
|
|
|
document.write('<scr'+'ipt type="text/javascript" src="' + CMsettings.path + CMsettings.jsFiles[i] + '"></scr'+'ipt>');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Borrowed from codemirror.js themeChanged function. Sets the theme's class names to the html element.
|
|
|
|
// Without this, the background color outside of the codemirror wrapper element remains white.
|
|
|
|
// [TMP] commented temporary, cause JS error: Uncaught TypeError: Cannot read property 'replace' of undefined
|
|
|
|
if(CMsettings.config.theme) {
|
|
|
|
document.documentElement.className += CMsettings.config.theme.replace(/(^|\s)\s*/g, " cm-s-");
|
|
|
|
}
|
|
|
|
|
|
|
|
window.onload = start;
|
|
|
|
}());
|
|
|
|
|
|
|
|
function start()
|
|
|
|
{// Initialise (on load)
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
if (typeof(window.CodeMirror) !== 'function') {
|
|
|
|
alert('CodeMirror not found in "' + CMsettings.path + '", aborting...');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create legend for keyboard shortcuts for find & replace:
|
2020-02-20 11:29:27 +01:00
|
|
|
var head = parent.document.querySelectorAll((tinymce.majorVersion < 5) ? '.mce-foot': '.tox-dialog__footer')[0],
|
2019-01-22 21:09:09 +01:00
|
|
|
div = parent.document.createElement('div'),
|
|
|
|
td1 = '<td style="font-size:11px;background:#777;color:#fff;padding:0 4px">',
|
|
|
|
td2 = '<td style="font-size:11px;padding-right:5px">';
|
2020-03-22 16:38:03 +01:00
|
|
|
div.innerHTML = '<table cellspacing="0" cellpadding="0" style="border-spacing:4px"><tr>' + td1 + (isMac ? '⌘-F' : 'Ctrl-F</td>') + td2 + tinymce.translate('Start search') + '</td>' + td1 + (isMac ? '⌘-G' : 'Ctrl-G') + '</td>' + td2 + tinymce.translate('Find next') + '</td>' + td1 + (isMac ? '⌘-Alt-G' : 'Shift-Ctrl-G') + '</td>' + td2 + tinymce.translate('Find previous') + '</td></tr>' + '<tr>' + td1 + (isMac ? '⌘-Alt-F' : 'Shift-Ctrl-F') + '</td>' + td2 + tinymce.translate('Replace') + '</td>' + td1 + (isMac ? 'Shift-⌘-Alt-F' : 'Shift-Ctrl-R') +'</td>' + td2 + tinymce.translate('Replace all') + '</td></tr></table>';
|
2019-01-22 21:09:09 +01:00
|
|
|
div.style.position = 'absolute';
|
|
|
|
div.style.left = div.style.bottom = '5px';
|
|
|
|
head.appendChild(div);
|
|
|
|
|
|
|
|
// Set CodeMirror cursor and bookmark to same position as cursor was in TinyMCE:
|
|
|
|
var html = editor.getContent({source_view: true});
|
|
|
|
|
|
|
|
// [FIX] #6 z-index issue with table panel and source code dialog
|
|
|
|
// editor.selection.getBookmark();
|
2020-02-20 11:29:27 +01:00
|
|
|
|
2019-01-22 21:09:09 +01:00
|
|
|
html = html.replace(/<span\s+style="display: none;"\s+class="CmCaReT"([^>]*)>([^<]*)<\/span>/gm, String.fromCharCode(chr));
|
|
|
|
editor.dom.remove(editor.dom.select('.CmCaReT'));
|
2020-02-20 11:29:27 +01:00
|
|
|
|
2019-01-22 21:09:09 +01:00
|
|
|
// Hide TinyMCE toolbar panels, [FIX] #6 z-index issue with table panel and source code dialog
|
|
|
|
// https://github.com/christiaan/tinymce-codemirror/issues/6
|
|
|
|
tinymce.each(editor.contextToolbars, function(toolbar) { if (toolbar.panel) { toolbar.panel.hide(); } });
|
2020-02-20 11:29:27 +01:00
|
|
|
|
2019-01-22 21:09:09 +01:00
|
|
|
CodeMirror.defineInitHook(function(inst)
|
|
|
|
{
|
|
|
|
// Move cursor to correct position:
|
|
|
|
inst.focus();
|
|
|
|
var cursor = inst.getSearchCursor(String.fromCharCode(chr), false);
|
|
|
|
if (cursor.findNext()) {
|
|
|
|
inst.setCursor(cursor.to());
|
|
|
|
cursor.replace('');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Indent all code, if so requested:
|
|
|
|
if (editor.settings.codemirror.indentOnInit) {
|
|
|
|
var last = inst.lineCount();
|
|
|
|
inst.operation(function() {
|
|
|
|
for (var i = 0; i < last; ++i) {
|
|
|
|
inst.indentLine(i);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
CMsettings.config.value = html;
|
|
|
|
|
|
|
|
// Instantiante CodeMirror:
|
|
|
|
codemirror = CodeMirror(document.body, CMsettings.config);
|
|
|
|
codemirror.isDirty = false;
|
|
|
|
codemirror.on('change', function(inst) {
|
|
|
|
inst.isDirty = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function findDepth(haystack, needle)
|
|
|
|
{
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var idx = haystack.indexOf(needle), depth = 0, x;
|
|
|
|
for (x = idx -1; x >= 0; x--) {
|
|
|
|
switch(haystack.charAt(x)) {
|
|
|
|
case '<': depth--; break;
|
|
|
|
case '>': depth++; break;
|
|
|
|
case '&': depth++; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return depth;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function is called by plugin.js, when user clicks 'Ok' button
|
|
|
|
function submit()
|
|
|
|
{
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var cc = '�', isDirty = codemirror.isDirty, doc = codemirror.doc;
|
|
|
|
|
|
|
|
if (doc.somethingSelected()) {
|
|
|
|
// Clear selection:
|
|
|
|
doc.setCursor(doc.getCursor());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert cursor placeholder (�)
|
|
|
|
doc.replaceSelection(cc);
|
|
|
|
|
|
|
|
var pos = codemirror.getCursor(),
|
|
|
|
curLineHTML = doc.getLine(pos.line);
|
|
|
|
|
|
|
|
if (findDepth(curLineHTML, cc) !== 0) {
|
|
|
|
// Cursor is inside a <tag>, don't set cursor:
|
|
|
|
curLineHTML = curLineHTML.replace(cc, '');
|
|
|
|
doc.replaceRange(curLineHTML, CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Submit HTML to TinyMCE:
|
|
|
|
// [FIX] Cursor position inside JS, style or &nbps;
|
|
|
|
// Workaround to fix cursor position if inside script tag
|
|
|
|
var code = codemirror.getValue();
|
|
|
|
if(code.search(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi) !== -1 || code.search(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi) !== -1)
|
|
|
|
{
|
|
|
|
editor.setContent(codemirror.getValue().replace(cc, ''));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
editor.setContent(codemirror.getValue().replace(cc, '<span id="CmCaReT"></span>'));
|
|
|
|
}
|
2020-02-20 11:29:27 +01:00
|
|
|
|
2019-01-22 21:09:09 +01:00
|
|
|
editor.isNotDirty = !isDirty;
|
|
|
|
if (isDirty) {
|
|
|
|
editor.nodeChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set cursor:
|
|
|
|
var el = editor.dom.select('span#CmCaReT')[0];
|
|
|
|
if (el) {
|
|
|
|
editor.selection.scrollIntoView(el);
|
|
|
|
editor.selection.setCursorLocation(el,0);
|
|
|
|
editor.dom.remove(el);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
</script>
|
2020-02-20 11:29:27 +01:00
|
|
|
<style type="text/css">
|
|
|
|
|
2019-01-22 21:09:09 +01:00
|
|
|
html,body { height:100%; }
|
|
|
|
body {
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.CodeMirror {
|
|
|
|
height: 100%;
|
|
|
|
font-size: 12px;
|
|
|
|
line-height: 18px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.CodeMirror-activeline-background {
|
|
|
|
background: #e8f2ff !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
.cm-trailingspace {
|
|
|
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==);
|
|
|
|
background-position: bottom left;
|
|
|
|
background-repeat: repeat-x;
|
|
|
|
}
|
|
|
|
</style>
|
2020-02-20 11:29:27 +01:00
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
</body>
|
2019-01-22 21:09:09 +01:00
|
|
|
</html>
|