mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-21 16:06:16 -04:00
Merge branch 'develop' into express-v3
Conflicts: src/node/hooks/express/errorhandling.js
This commit is contained in:
commit
2684a1d295
23 changed files with 628 additions and 174 deletions
|
@ -35,6 +35,7 @@ var cleanText = require("./Pad").cleanText;
|
|||
/**GROUP FUNCTIONS*****/
|
||||
/**********************/
|
||||
|
||||
exports.listAllGroups = groupManager.listAllGroups;
|
||||
exports.createGroup = groupManager.createGroup;
|
||||
exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor;
|
||||
exports.deleteGroup = groupManager.deleteGroup;
|
||||
|
|
|
@ -26,6 +26,24 @@ var db = require("./DB").db;
|
|||
var async = require("async");
|
||||
var padManager = require("./PadManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
|
||||
exports.listAllGroups = function(callback) {
|
||||
db.get("groups", function (err, groups) {
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
// there are no groups
|
||||
if(groups == null) {
|
||||
callback(null, {groupIDs: []});
|
||||
return;
|
||||
}
|
||||
|
||||
var groupIDs = [];
|
||||
for ( var groupID in groups ) {
|
||||
groupIDs.push(groupID);
|
||||
}
|
||||
callback(null, {groupIDs: groupIDs});
|
||||
});
|
||||
}
|
||||
|
||||
exports.deleteGroup = function(groupID, callback)
|
||||
{
|
||||
|
@ -105,6 +123,39 @@ exports.deleteGroup = function(groupID, callback)
|
|||
db.remove("group2sessions:" + groupID);
|
||||
db.remove("group:" + groupID);
|
||||
callback();
|
||||
},
|
||||
//unlist the group
|
||||
function(callback)
|
||||
{
|
||||
exports.listAllGroups(function(err, groups) {
|
||||
if(ERR(err, callback)) return;
|
||||
groups = groups? groups.groupIDs : [];
|
||||
|
||||
// it's not listed
|
||||
if(groups.indexOf(groupID) == -1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
groups.splice(groups.indexOf(groupID), 1);
|
||||
|
||||
// store empty groupe list
|
||||
if(groups.length == 0) {
|
||||
db.set("groups", {});
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// regenerate group list
|
||||
var newGroups = {};
|
||||
async.forEach(groups, function(group, cb) {
|
||||
newGroups[group] = 1;
|
||||
cb();
|
||||
},function() {
|
||||
db.set("groups", newGroups);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
|
@ -130,7 +181,24 @@ exports.createGroup = function(callback)
|
|||
|
||||
//create the group
|
||||
db.set("group:" + groupID, {pads: {}});
|
||||
callback(null, {groupID: groupID});
|
||||
|
||||
//list the group
|
||||
exports.listAllGroups(function(err, groups) {
|
||||
if(ERR(err, callback)) return;
|
||||
groups = groups? groups.groupIDs : [];
|
||||
|
||||
groups.push(groupID);
|
||||
|
||||
// regenerate group list
|
||||
var newGroups = {};
|
||||
async.forEach(groups, function(group, cb) {
|
||||
newGroups[group] = 1;
|
||||
cb();
|
||||
},function() {
|
||||
db.set("groups", newGroups);
|
||||
callback(null, {groupID: groupID});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.createGroupIfNotExistsFor = function(groupMapper, callback)
|
||||
|
|
|
@ -123,7 +123,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
|
|||
}
|
||||
|
||||
var sessionIDs = sessionCookie.split(',');
|
||||
async.foreach(sessionIDs, function(sessionID) {
|
||||
async.forEach(sessionIDs, function(sessionID, callback) {
|
||||
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) {
|
||||
//skip session if it doesn't exist
|
||||
if(err && err.message == "sessionID does not exist") return;
|
||||
|
@ -141,8 +141,10 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
|
|||
// There is a valid session
|
||||
validSession = true;
|
||||
sessionAuthor = sessionInfo.authorID;
|
||||
|
||||
callback();
|
||||
});
|
||||
}, callback)
|
||||
}, callback);
|
||||
},
|
||||
//get author for token
|
||||
function(callback)
|
||||
|
|
|
@ -40,6 +40,36 @@ catch(e)
|
|||
//a list of all functions
|
||||
var version =
|
||||
{ "1":
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "setPassword" : ["padID", "password"]
|
||||
, "isPasswordProtected" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
}
|
||||
, "1.1":
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
|
@ -71,6 +101,7 @@ var version =
|
|||
, "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -436,15 +436,22 @@ function handleUserInfoUpdate(client, message)
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
|
||||
* This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly
|
||||
* Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
|
||||
* Handles a USER_CHANGES message, where the client submits its local
|
||||
* edits as a changeset.
|
||||
*
|
||||
* This handler's job is to update the incoming changeset so that it applies
|
||||
* to the latest revision, then add it to the pad, broadcast the changes
|
||||
* to all other clients, and send a confirmation to the submitting client.
|
||||
*
|
||||
* This function is based on a similar one in the original Etherpad.
|
||||
* See https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
|
||||
*
|
||||
* @param client the client that send this message
|
||||
* @param message the message from the client
|
||||
*/
|
||||
function handleUserChanges(client, message)
|
||||
{
|
||||
//check if all ok
|
||||
// Make sure all required fields are present
|
||||
if(message.data.baseRev == null)
|
||||
{
|
||||
messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!");
|
||||
|
@ -465,6 +472,9 @@ function handleUserChanges(client, message)
|
|||
var baseRev = message.data.baseRev;
|
||||
var wireApool = (new AttributePool()).fromJsonable(message.data.apool);
|
||||
var changeset = message.data.changeset;
|
||||
// The client might disconnect between our callbacks. We should still
|
||||
// finish processing the changeset, so keep a reference to the session.
|
||||
var thisSession = sessioninfos[client.id];
|
||||
|
||||
var r, apool, pad;
|
||||
|
||||
|
@ -472,7 +482,7 @@ function handleUserChanges(client, message)
|
|||
//get the pad
|
||||
function(callback)
|
||||
{
|
||||
padManager.getPad(sessioninfos[client.id].padId, function(err, value)
|
||||
padManager.getPad(thisSession.padId, function(err, value)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
pad = value;
|
||||
|
@ -484,22 +494,23 @@ function handleUserChanges(client, message)
|
|||
{
|
||||
//ex. _checkChangesetAndPool
|
||||
|
||||
//Copied from Etherpad, don't know what it does exactly
|
||||
try
|
||||
{
|
||||
//this looks like a changeset check, it throws errors sometimes
|
||||
// Verify that the changeset has valid syntax and is in canonical form
|
||||
Changeset.checkRep(changeset);
|
||||
|
||||
|
||||
// Verify that the attribute indexes used in the changeset are all
|
||||
// defined in the accompanying attribute pool.
|
||||
Changeset.eachAttribNumber(changeset, function(n) {
|
||||
if (! wireApool.getAttrib(n)) {
|
||||
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
|
||||
}
|
||||
});
|
||||
}
|
||||
//there is an error in this changeset, so just refuse it
|
||||
catch(e)
|
||||
{
|
||||
console.warn("Can't apply USER_CHANGES "+changeset+", cause it faild checkRep");
|
||||
// There is an error in this changeset, so just refuse it
|
||||
console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep");
|
||||
client.json.send({disconnect:"badChangeset"});
|
||||
return;
|
||||
}
|
||||
|
@ -512,7 +523,10 @@ function handleUserChanges(client, message)
|
|||
//ex. applyUserChanges
|
||||
apool = pad.pool;
|
||||
r = baseRev;
|
||||
|
||||
|
||||
// The client's changeset might not be based on the latest revision,
|
||||
// since other clients are sending changes at the same time.
|
||||
// Update the changeset so that it can be applied to the latest revision.
|
||||
//https://github.com/caolan/async#whilst
|
||||
async.whilst(
|
||||
function() { return r < pad.getHeadRevisionNumber(); },
|
||||
|
@ -523,9 +537,18 @@ function handleUserChanges(client, message)
|
|||
pad.getRevisionChangeset(r, function(err, c)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
|
||||
// At this point, both "c" (from the pad) and "changeset" (from the
|
||||
// client) are relative to revision r - 1. The follow function
|
||||
// rebases "changeset" so that it is relative to revision r
|
||||
// and can be applied after "c".
|
||||
changeset = Changeset.follow(c, changeset, false, apool);
|
||||
callback(null);
|
||||
|
||||
if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep
|
||||
async.nextTick(callback);
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
//use the callback of the series function
|
||||
|
@ -545,15 +568,14 @@ function handleUserChanges(client, message)
|
|||
return;
|
||||
}
|
||||
|
||||
var thisAuthor = sessioninfos[client.id].author;
|
||||
|
||||
pad.appendRevision(changeset, thisAuthor);
|
||||
pad.appendRevision(changeset, thisSession.author);
|
||||
|
||||
var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool);
|
||||
if (correctionChangeset) {
|
||||
pad.appendRevision(correctionChangeset);
|
||||
}
|
||||
|
||||
|
||||
// Make sure the pad always ends with an empty line.
|
||||
if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
|
||||
var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n");
|
||||
pad.appendRevision(nlChangeset);
|
||||
|
@ -860,6 +882,13 @@ function handleClientReady(client, message)
|
|||
},
|
||||
function(callback)
|
||||
{
|
||||
//Check that the client is still here. It might have disconnected between callbacks.
|
||||
if(sessioninfos[client.id] === undefined)
|
||||
{
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if this author is already on the pad, if yes, kick the other sessions!
|
||||
if(pad2sessions[padIds.padId])
|
||||
{
|
||||
|
@ -874,10 +903,9 @@ function handleClientReady(client, message)
|
|||
}
|
||||
|
||||
//Save in sessioninfos that this session belonges to this pad
|
||||
var sessionId=String(client.id);
|
||||
sessioninfos[sessionId].padId = padIds.padId;
|
||||
sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId;
|
||||
sessioninfos[sessionId].readonly = padIds.readonly;
|
||||
sessioninfos[client.id].padId = padIds.padId;
|
||||
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
|
||||
sessioninfos[client.id].readonly = padIds.readonly;
|
||||
|
||||
//check if there is already a pad2sessions entry, if not, create one
|
||||
if(!pad2sessions[padIds.padId])
|
||||
|
@ -886,7 +914,7 @@ function handleClientReady(client, message)
|
|||
}
|
||||
|
||||
//Saves in pad2sessions that this session belongs to this pad
|
||||
pad2sessions[padIds.padId].push(sessionId);
|
||||
pad2sessions[padIds.padId].push(client.id);
|
||||
|
||||
//prepare all values for the wire
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
|
@ -951,26 +979,22 @@ function handleClientReady(client, message)
|
|||
clientVars.userName = authorName;
|
||||
}
|
||||
|
||||
if(sessioninfos[client.id] !== undefined)
|
||||
//If this is a reconnect, we don't have to send the client the ClientVars again
|
||||
if(message.reconnect == true)
|
||||
{
|
||||
//This is a reconnect, so we don't have to send the client the ClientVars again
|
||||
if(message.reconnect == true)
|
||||
{
|
||||
//Save the revision in sessioninfos, we take the revision from the info the client send to us
|
||||
sessioninfos[client.id].rev = message.client_rev;
|
||||
}
|
||||
//This is a normal first connect
|
||||
else
|
||||
{
|
||||
//Send the clientVars to the Client
|
||||
client.json.send({type: "CLIENT_VARS", data: clientVars});
|
||||
//Save the revision in sessioninfos
|
||||
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
|
||||
}
|
||||
|
||||
//Save the revision and the author id in sessioninfos
|
||||
sessioninfos[client.id].author = author;
|
||||
//Save the revision in sessioninfos, we take the revision from the info the client send to us
|
||||
sessioninfos[client.id].rev = message.client_rev;
|
||||
}
|
||||
//This is a normal first connect
|
||||
else
|
||||
{
|
||||
//Send the clientVars to the Client
|
||||
client.json.send({type: "CLIENT_VARS", data: clientVars});
|
||||
//Save the current revision in sessioninfos, should be the same as in clientVars
|
||||
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
|
||||
}
|
||||
|
||||
sessioninfos[client.id].author = author;
|
||||
|
||||
//prepare the notification for the other users on the pad, that this user joined
|
||||
var messageToTheOtherUsers = {
|
||||
|
|
|
@ -39,11 +39,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// allowing you to respond however you like
|
||||
res.send(500, { error: 'Sorry, something bad happened!' });
|
||||
console.error(err.stack? err.stack : err.toString());
|
||||
exports.gracefulShutdown();
|
||||
next();
|
||||
})
|
||||
|
||||
|
||||
//connect graceful shutdown with sigint and uncaughtexception
|
||||
if(os.type().indexOf("Windows") == -1) {
|
||||
//sigint is so far not working on windows
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"name": "Robin Buse" }
|
||||
],
|
||||
"dependencies" : {
|
||||
"yajsml" : "1.1.4",
|
||||
"yajsml" : "1.1.5",
|
||||
"request" : "2.9.100",
|
||||
"require-kernel" : "1.0.5",
|
||||
"resolve" : "0.2.x",
|
||||
|
@ -42,5 +42,5 @@
|
|||
"engines" : { "node" : ">=0.6.0",
|
||||
"npm" : ">=1.0"
|
||||
},
|
||||
"version" : "1.0.0"
|
||||
"version" : "1.1.2"
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
// requires: plugins
|
||||
// requires: undefined
|
||||
|
||||
var KERNEL_SOURCE = '../static/js/require-kernel.js';
|
||||
|
||||
Ace2Editor.registry = {
|
||||
nextId: 1
|
||||
};
|
||||
|
@ -31,6 +33,14 @@ Ace2Editor.registry = {
|
|||
var hooks = require('./pluginfw/hooks');
|
||||
var _ = require('./underscore');
|
||||
|
||||
function scriptTag(source) {
|
||||
return (
|
||||
'<script type="text/javascript">\n'
|
||||
+ source.replace(/<\//g, '<\\/') +
|
||||
'</script>'
|
||||
)
|
||||
}
|
||||
|
||||
function Ace2Editor()
|
||||
{
|
||||
var ace2 = Ace2Editor;
|
||||
|
@ -155,24 +165,6 @@ function Ace2Editor()
|
|||
|
||||
return {embeded: embededFiles, remote: remoteFiles};
|
||||
}
|
||||
function pushRequireScriptTo(buffer) {
|
||||
var KERNEL_SOURCE = '../static/js/require-kernel.js';
|
||||
var KERNEL_BOOT = '\
|
||||
require.setRootURI("../javascripts/src");\n\
|
||||
require.setLibraryURI("../javascripts/lib");\n\
|
||||
require.setGlobalKeyPath("require");\n\
|
||||
';
|
||||
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
|
||||
buffer.push('<script type="text/javascript">');
|
||||
buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]);
|
||||
buffer.push(KERNEL_BOOT);
|
||||
buffer.push('<\/script>');
|
||||
} else {
|
||||
// Remotely src'd script tag will not work in IE; it must be embedded, so
|
||||
// throw an error if it is not.
|
||||
throw new Error("Require script could not be embedded.");
|
||||
}
|
||||
}
|
||||
function pushStyleTagsFor(buffer, files) {
|
||||
var sorted = sortFilesByEmbeded(files);
|
||||
var embededFiles = sorted.embeded;
|
||||
|
@ -236,23 +228,30 @@ require.setGlobalKeyPath("require");\n\
|
|||
|
||||
pushStyleTagsFor(iframeHTML, includedCSS);
|
||||
|
||||
var includedJS = [];
|
||||
pushRequireScriptTo(iframeHTML);
|
||||
if (!Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
|
||||
// Remotely src'd script tag will not work in IE; it must be embedded, so
|
||||
// throw an error if it is not.
|
||||
throw new Error("Require kernel could not be found.");
|
||||
}
|
||||
|
||||
// Inject my plugins into my child.
|
||||
iframeHTML.push('\
|
||||
<script type="text/javascript">\n\
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");\n\
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/client_plugins");\n\
|
||||
hooks.plugins = plugins;\n\
|
||||
plugins.adoptPluginsFromAncestorsOf(window);\n\
|
||||
</script>\
|
||||
');
|
||||
|
||||
iframeHTML.push('<script type="text/javascript">');
|
||||
iframeHTML.push('$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK');
|
||||
iframeHTML.push('require("ep_etherpad-lite/static/js/ace2_inner");');
|
||||
iframeHTML.push('<\/script>');
|
||||
iframeHTML.push(scriptTag(
|
||||
Ace2Editor.EMBEDED[KERNEL_SOURCE] + '\n\
|
||||
require.setRootURI("../javascripts/src");\n\
|
||||
require.setLibraryURI("../javascripts/lib");\n\
|
||||
require.setGlobalKeyPath("require");\n\
|
||||
\n\
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");\n\
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/client_plugins");\n\
|
||||
hooks.plugins = plugins;\n\
|
||||
plugins.adoptPluginsFromAncestorsOf(window);\n\
|
||||
\n\
|
||||
$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").jQuery; // Expose jQuery #HACK\n\
|
||||
var Ace2Inner = require("ep_etherpad-lite/static/js/ace2_inner");\n\
|
||||
\n\
|
||||
plugins.ensure(function () {\n\
|
||||
Ace2Inner.init();\n\
|
||||
});\n\
|
||||
'));
|
||||
|
||||
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
|
||||
|
||||
|
@ -266,8 +265,32 @@ require.setGlobalKeyPath("require");\n\
|
|||
var thisFunctionsName = "ChildAccessibleAce2Editor";
|
||||
(function () {return this}())[thisFunctionsName] = Ace2Editor;
|
||||
|
||||
var outerScript = 'editorId = "' + info.id + '"; editorInfo = parent.' + thisFunctionsName + '.registry[editorId]; ' + 'window.onload = function() ' + '{ window.onload = null; setTimeout' + '(function() ' + '{ var iframe = document.createElement("IFRAME"); iframe.name = "ace_inner";' + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); ' + 'iframe.frameBorder = 0; iframe.allowTransparency = true; ' + // for IE
|
||||
'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');doc.write(text); doc.close(); ' + '}, 0); }';
|
||||
var outerScript = '\
|
||||
editorId = ' + JSON.stringify(info.id) + ';\n\
|
||||
editorInfo = parent[' + JSON.stringify(thisFunctionsName) + '].registry[editorId];\n\
|
||||
window.onload = function () {\n\
|
||||
window.onload = null;\n\
|
||||
setTimeout(function () {\n\
|
||||
var iframe = document.createElement("IFRAME");\n\
|
||||
iframe.name = "ace_inner";\n\
|
||||
iframe.scrolling = "no";\n\
|
||||
var outerdocbody = document.getElementById("outerdocbody");\n\
|
||||
iframe.frameBorder = 0;\n\
|
||||
iframe.allowTransparency = true; // for IE\n\
|
||||
outerdocbody.insertBefore(iframe, outerdocbody.firstChild);\n\
|
||||
iframe.ace_outerWin = window;\n\
|
||||
readyFunc = function () {\n\
|
||||
editorInfo.onEditorReady();\n\
|
||||
readyFunc = null;\n\
|
||||
editorInfo = null;\n\
|
||||
};\n\
|
||||
var doc = iframe.contentWindow.document;\n\
|
||||
doc.open();\n\
|
||||
var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');\n\
|
||||
doc.write(text);\n\
|
||||
doc.close();\n\
|
||||
}, 0);\n\
|
||||
}';
|
||||
|
||||
var outerHTML = [doctype, '<html><head>']
|
||||
|
||||
|
@ -285,7 +308,7 @@ require.setGlobalKeyPath("require");\n\
|
|||
|
||||
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
|
||||
// (throbs busy while typing)
|
||||
outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript.replace(/<\//g, '<\\/'), '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>');
|
||||
outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', scriptTag(outerScript), '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>');
|
||||
|
||||
var outerFrame = document.createElement("IFRAME");
|
||||
outerFrame.name = "ace_outer";
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var editor, _, $, jQuery, plugins, Ace2Common;
|
||||
var _, $, jQuery, plugins, Ace2Common;
|
||||
|
||||
Ace2Common = require('./ace2_common');
|
||||
|
||||
|
@ -200,6 +200,11 @@ function Ace2Inner(){
|
|||
|
||||
var authorInfos = {}; // presence of key determines if author is present in doc
|
||||
|
||||
function getAuthorInfos(){
|
||||
return authorInfos;
|
||||
};
|
||||
editorInfo.ace_getAuthorInfos= getAuthorInfos;
|
||||
|
||||
function setAuthorInfo(author, info)
|
||||
{
|
||||
if ((typeof author) != "string")
|
||||
|
@ -884,7 +889,14 @@ function Ace2Inner(){
|
|||
editorInfo.ace_setEditable = setEditable;
|
||||
editorInfo.ace_execCommand = execCommand;
|
||||
editorInfo.ace_replaceRange = replaceRange;
|
||||
|
||||
editorInfo.ace_getAuthorInfos= getAuthorInfos;
|
||||
editorInfo.ace_performDocumentReplaceRange = performDocumentReplaceRange;
|
||||
editorInfo.ace_performDocumentReplaceCharRange = performDocumentReplaceCharRange;
|
||||
editorInfo.ace_renumberList = renumberList;
|
||||
editorInfo.ace_doReturnKey = doReturnKey;
|
||||
editorInfo.ace_isBlockElement = isBlockElement;
|
||||
editorInfo.ace_getLineListType = getLineListType;
|
||||
|
||||
editorInfo.ace_callWithAce = function(fn, callStack, normalize)
|
||||
{
|
||||
var wrapper = function()
|
||||
|
@ -1161,7 +1173,7 @@ function Ace2Inner(){
|
|||
//if (! top.BEFORE) top.BEFORE = [];
|
||||
//top.BEFORE.push(magicdom.root.dom.innerHTML);
|
||||
//if (! isEditable) return; // and don't reschedule
|
||||
if (inInternationalComposition)
|
||||
if (window.parent.parent.inInternationalComposition)
|
||||
{
|
||||
// don't do idle input incorporation during international input composition
|
||||
idleWorkTimer.atLeast(500);
|
||||
|
@ -1486,7 +1498,6 @@ function Ace2Inner(){
|
|||
|
||||
if (currentCallStack.domClean) return false;
|
||||
|
||||
inInternationalComposition = false; // if we need the document normalized, so be it
|
||||
currentCallStack.isUserChange = true;
|
||||
|
||||
isTimeUp = (isTimeUp ||
|
||||
|
@ -1686,11 +1697,27 @@ function Ace2Inner(){
|
|||
if (selection && !selStart)
|
||||
{
|
||||
//if (domChanges) dmesg("selection not collected");
|
||||
selStart = getLineAndCharForPoint(selection.startPoint);
|
||||
var selStartFromHook = hooks.callAll('aceStartLineAndCharForPoint', {
|
||||
callstack: currentCallStack,
|
||||
editorInfo: editorInfo,
|
||||
rep: rep,
|
||||
root:root,
|
||||
point:selection.startPoint,
|
||||
documentAttributeManager: documentAttributeManager
|
||||
});
|
||||
selStart = (selStartFromHook==null||selStartFromHook.length==0)?getLineAndCharForPoint(selection.startPoint):selStartFromHook;
|
||||
}
|
||||
if (selection && !selEnd)
|
||||
{
|
||||
selEnd = getLineAndCharForPoint(selection.endPoint);
|
||||
var selEndFromHook = hooks.callAll('aceEndLineAndCharForPoint', {
|
||||
callstack: currentCallStack,
|
||||
editorInfo: editorInfo,
|
||||
rep: rep,
|
||||
root:root,
|
||||
point:selection.endPoint,
|
||||
documentAttributeManager: documentAttributeManager
|
||||
});
|
||||
selEnd = (selEndFromHook==null||selEndFromHook.length==0)?getLineAndCharForPoint(selection.endPoint):selEndFromHook;
|
||||
}
|
||||
|
||||
// selection from content collection can, in various ways, extend past final
|
||||
|
@ -1845,17 +1872,20 @@ function Ace2Inner(){
|
|||
{
|
||||
return rep.selStart[0];
|
||||
}
|
||||
|
||||
editorInfo.ace_caretLine = caretLine;
|
||||
|
||||
function caretColumn()
|
||||
{
|
||||
return rep.selStart[1];
|
||||
}
|
||||
|
||||
editorInfo.ace_caretColumn = caretColumn;
|
||||
|
||||
function caretDocChar()
|
||||
{
|
||||
return rep.lines.offsetOfIndex(caretLine()) + caretColumn();
|
||||
}
|
||||
|
||||
editorInfo.ace_caretDocChar = caretDocChar;
|
||||
|
||||
function handleReturnIndentation()
|
||||
{
|
||||
// on return, indent to level of previous line
|
||||
|
@ -3447,7 +3477,8 @@ function Ace2Inner(){
|
|||
{
|
||||
return !!REGEX_WORDCHAR.exec(c);
|
||||
}
|
||||
|
||||
editorInfo.ace_isWordChar = isWordChar;
|
||||
|
||||
function isSpaceChar(c)
|
||||
{
|
||||
return !!REGEX_SPACE.exec(c);
|
||||
|
@ -3555,7 +3586,15 @@ function Ace2Inner(){
|
|||
|
||||
if (!stopped)
|
||||
{
|
||||
if (isTypeForSpecialKey && keyCode == 8)
|
||||
var specialHandledInHook = hooks.callAll('aceKeyEvent', {
|
||||
callstack: currentCallStack,
|
||||
editorInfo: editorInfo,
|
||||
rep: rep,
|
||||
documentAttributeManager: documentAttributeManager,
|
||||
evt:evt
|
||||
});
|
||||
specialHandled = (specialHandledInHook&&specialHandledInHook.length>0)?specialHandledInHook[0]:specialHandled;
|
||||
if ((!specialHandled) && isTypeForSpecialKey && keyCode == 8)
|
||||
{
|
||||
// "delete" key; in mozilla, if we're at the beginning of a line, normalize now,
|
||||
// or else deleting a blank line can take two delete presses.
|
||||
|
@ -3690,7 +3729,7 @@ function Ace2Inner(){
|
|||
thisKeyDoesntTriggerNormalize = true;
|
||||
}
|
||||
|
||||
if ((!specialHandled) && (!thisKeyDoesntTriggerNormalize) && (!inInternationalComposition))
|
||||
if ((!specialHandled) && (!thisKeyDoesntTriggerNormalize) && (!window.parent.parent.inInternationalComposition))
|
||||
{
|
||||
if (type != "keyup" || !incorpIfQuick())
|
||||
{
|
||||
|
@ -4550,19 +4589,9 @@ function Ace2Inner(){
|
|||
}
|
||||
}
|
||||
|
||||
var inInternationalComposition = false;
|
||||
|
||||
function handleCompositionEvent(evt)
|
||||
{
|
||||
// international input events, fired in FF3, at least; allow e.g. Japanese input
|
||||
if (evt.type == "compositionstart")
|
||||
{
|
||||
inInternationalComposition = true;
|
||||
}
|
||||
else if (evt.type == "compositionend")
|
||||
{
|
||||
inInternationalComposition = false;
|
||||
}
|
||||
window.parent.parent.handleCompositionEvent(evt);
|
||||
}
|
||||
|
||||
function bindTheEventHandlers()
|
||||
|
@ -4577,7 +4606,8 @@ function Ace2Inner(){
|
|||
$(document).on("click", handleIEOuterClick);
|
||||
}
|
||||
if (browser.msie) $(root).on("paste", handleIEPaste);
|
||||
if ((!browser.msie) && document.documentElement)
|
||||
// CompositionEvent is not implemented below IE version 8
|
||||
if ( !(browser.msie && browser.version < 9) && document.documentElement)
|
||||
{
|
||||
$(document.documentElement).on("compositionstart", handleCompositionEvent);
|
||||
$(document.documentElement).on("compositionend", handleCompositionEvent);
|
||||
|
@ -5400,62 +5430,64 @@ function Ace2Inner(){
|
|||
return documentAttributeManager.setAttributesOnRange.apply(documentAttributeManager, arguments);
|
||||
};
|
||||
|
||||
$(document).ready(function(){
|
||||
doc = document; // defined as a var in scope outside
|
||||
inCallStack("setup", function()
|
||||
{
|
||||
var body = doc.getElementById("innerdocbody");
|
||||
root = body; // defined as a var in scope outside
|
||||
if (browser.mozilla) $(root).addClass("mozilla");
|
||||
if (browser.safari) $(root).addClass("safari");
|
||||
if (browser.msie) $(root).addClass("msie");
|
||||
if (browser.msie)
|
||||
this.init = function () {
|
||||
$(document).ready(function(){
|
||||
doc = document; // defined as a var in scope outside
|
||||
inCallStack("setup", function()
|
||||
{
|
||||
// cache CSS background images
|
||||
try
|
||||
var body = doc.getElementById("innerdocbody");
|
||||
root = body; // defined as a var in scope outside
|
||||
if (browser.mozilla) $(root).addClass("mozilla");
|
||||
if (browser.safari) $(root).addClass("safari");
|
||||
if (browser.msie) $(root).addClass("msie");
|
||||
if (browser.msie)
|
||||
{
|
||||
doc.execCommand("BackgroundImageCache", false, true);
|
||||
// cache CSS background images
|
||||
try
|
||||
{
|
||||
doc.execCommand("BackgroundImageCache", false, true);
|
||||
}
|
||||
catch (e)
|
||||
{ /* throws an error in some IE 6 but not others! */
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{ /* throws an error in some IE 6 but not others! */
|
||||
}
|
||||
}
|
||||
setClassPresence(root, "authorColors", true);
|
||||
setClassPresence(root, "doesWrap", doesWrap);
|
||||
setClassPresence(root, "authorColors", true);
|
||||
setClassPresence(root, "doesWrap", doesWrap);
|
||||
|
||||
initDynamicCSS();
|
||||
initDynamicCSS();
|
||||
|
||||
enforceEditability();
|
||||
enforceEditability();
|
||||
|
||||
// set up dom and rep
|
||||
while (root.firstChild) root.removeChild(root.firstChild);
|
||||
var oneEntry = createDomLineEntry("");
|
||||
doRepLineSplice(0, rep.lines.length(), [oneEntry]);
|
||||
insertDomLines(null, [oneEntry.domInfo], null);
|
||||
rep.alines = Changeset.splitAttributionLines(
|
||||
Changeset.makeAttribution("\n"), "\n");
|
||||
// set up dom and rep
|
||||
while (root.firstChild) root.removeChild(root.firstChild);
|
||||
var oneEntry = createDomLineEntry("");
|
||||
doRepLineSplice(0, rep.lines.length(), [oneEntry]);
|
||||
insertDomLines(null, [oneEntry.domInfo], null);
|
||||
rep.alines = Changeset.splitAttributionLines(
|
||||
Changeset.makeAttribution("\n"), "\n");
|
||||
|
||||
bindTheEventHandlers();
|
||||
bindTheEventHandlers();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
hooks.callAll('aceInitialized', {
|
||||
editorInfo: editorInfo,
|
||||
rep: rep,
|
||||
documentAttributeManager: documentAttributeManager
|
||||
});
|
||||
hooks.callAll('aceInitialized', {
|
||||
editorInfo: editorInfo,
|
||||
rep: rep,
|
||||
documentAttributeManager: documentAttributeManager
|
||||
});
|
||||
|
||||
scheduler.setTimeout(function()
|
||||
{
|
||||
parent.readyFunc(); // defined in code that sets up the inner iframe
|
||||
}, 0);
|
||||
scheduler.setTimeout(function()
|
||||
{
|
||||
parent.readyFunc(); // defined in code that sets up the inner iframe
|
||||
}, 0);
|
||||
|
||||
isSetUp = true;
|
||||
});
|
||||
isSetUp = true;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ensure that plugins are loaded before initializing the editor
|
||||
plugins.ensure(function () {
|
||||
var editor = new Ace2Inner();
|
||||
});
|
||||
exports.init = function () {
|
||||
var editor = new Ace2Inner()
|
||||
editor.init();
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@ $(document).ready(function () {
|
|||
$('.search-results').data('query', {
|
||||
pattern: '',
|
||||
offset: 0,
|
||||
limit: 4,
|
||||
limit: 12,
|
||||
});
|
||||
|
||||
var doUpdate = false;
|
||||
|
|
|
@ -62,6 +62,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
var caughtErrorCatchers = [];
|
||||
var caughtErrorTimes = [];
|
||||
var debugMessages = [];
|
||||
var msgQueue = [];
|
||||
|
||||
tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData);
|
||||
tellAceActiveAuthorInfo(initialUserInfo);
|
||||
|
@ -110,6 +111,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
|
||||
function handleUserChanges()
|
||||
{
|
||||
if (window.parent.parent.inInternationalComposition) return;
|
||||
if ((!getSocket()) || channelState == "CONNECTING")
|
||||
{
|
||||
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000))
|
||||
|
@ -128,12 +130,12 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
|
||||
if (state != "IDLE")
|
||||
{
|
||||
if (state == "COMMITTING" && (t - lastCommitTime) > 20000)
|
||||
if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 20000)
|
||||
{
|
||||
// a commit is taking too long
|
||||
setChannelState("DISCONNECTED", "slowcommit");
|
||||
}
|
||||
else if (state == "COMMITTING" && (t - lastCommitTime) > 5000)
|
||||
else if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 5000)
|
||||
{
|
||||
callbacks.onConnectionTrouble("SLOW");
|
||||
}
|
||||
|
@ -152,6 +154,36 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
return;
|
||||
}
|
||||
|
||||
// apply msgQueue changeset.
|
||||
if (msgQueue.length != 0) {
|
||||
while (msg = msgQueue.shift()) {
|
||||
var newRev = msg.newRev;
|
||||
rev=newRev;
|
||||
if (msg.type == "ACCEPT_COMMIT")
|
||||
{
|
||||
editor.applyPreparedChangesetToBase();
|
||||
setStateIdle();
|
||||
callCatchingErrors("onInternalAction", function()
|
||||
{
|
||||
callbacks.onInternalAction("commitAcceptedByServer");
|
||||
});
|
||||
callCatchingErrors("onConnectionTrouble", function()
|
||||
{
|
||||
callbacks.onConnectionTrouble("OK");
|
||||
});
|
||||
handleUserChanges();
|
||||
}
|
||||
else if (msg.type == "NEW_CHANGES")
|
||||
{
|
||||
var changeset = msg.changeset;
|
||||
var author = (msg.author || '');
|
||||
var apool = msg.apool;
|
||||
|
||||
editor.applyChangesToBase(changeset, author, apool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sentMessage = false;
|
||||
var userChangesData = editor.prepareUserChangeset();
|
||||
if (userChangesData.changeset)
|
||||
|
@ -254,6 +286,22 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
var changeset = msg.changeset;
|
||||
var author = (msg.author || '');
|
||||
var apool = msg.apool;
|
||||
|
||||
// When inInternationalComposition, msg pushed msgQueue.
|
||||
if (msgQueue.length > 0 || window.parent.parent.inInternationalComposition) {
|
||||
if (msgQueue.length > 0) oldRev = msgQueue[msgQueue.length - 1].newRev;
|
||||
else oldRev = rev;
|
||||
|
||||
if (newRev != (oldRev + 1))
|
||||
{
|
||||
dmesg("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1));
|
||||
setChannelState("DISCONNECTED", "badmessage_newchanges");
|
||||
return;
|
||||
}
|
||||
msgQueue.push(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newRev != (rev + 1))
|
||||
{
|
||||
dmesg("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
|
||||
|
@ -266,6 +314,18 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
|
|||
else if (msg.type == "ACCEPT_COMMIT")
|
||||
{
|
||||
var newRev = msg.newRev;
|
||||
if (msgQueue.length > 0)
|
||||
{
|
||||
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1))
|
||||
{
|
||||
dmesg("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
|
||||
setChannelState("DISCONNECTED", "badmessage_acceptcommit");
|
||||
return;
|
||||
}
|
||||
msgQueue.push(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newRev != (rev + 1))
|
||||
{
|
||||
dmesg("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
|
||||
|
|
|
@ -375,6 +375,19 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
|
|||
if (dom.isNodeText(node))
|
||||
{
|
||||
var txt = dom.nodeValue(node);
|
||||
var tname = dom.nodeAttr(node.parentNode,"name");
|
||||
|
||||
var txtFromHook = hooks.callAll('collectContentLineText', {
|
||||
cc: this,
|
||||
state: state,
|
||||
tname: tname,
|
||||
node:node,
|
||||
text:txt,
|
||||
styl: null,
|
||||
cls: null
|
||||
});
|
||||
var txt = (typeof(txtFromHook)=='object'&&txtFromHook.length==0)?dom.nodeValue(node):txtFromHook[0];
|
||||
|
||||
var rest = '';
|
||||
var x = 0; // offset into original text
|
||||
if (txt.length == 0)
|
||||
|
@ -386,7 +399,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
|
|||
if (endPoint && node == endPoint.node)
|
||||
{
|
||||
selEnd = _pointHere(0, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (txt.length > 0)
|
||||
{
|
||||
|
@ -441,8 +454,21 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
|
|||
{
|
||||
var tname = (dom.nodeTagName(node) || "").toLowerCase();
|
||||
if (tname == "br")
|
||||
{
|
||||
cc.startNewLine(state);
|
||||
{
|
||||
this.breakLine = true;
|
||||
var tvalue = dom.nodeAttr(node, 'value');
|
||||
var induceLineBreak = hooks.callAll('collectContentLineBreak', {
|
||||
cc: this,
|
||||
state: state,
|
||||
tname: tname,
|
||||
tvalue:tvalue,
|
||||
styl: null,
|
||||
cls: null
|
||||
});
|
||||
var startNewLine= (typeof(induceLineBreak)=='object'&&induceLineBreak.length==0)?true:induceLineBreak[0];
|
||||
if(startNewLine){
|
||||
cc.startNewLine(state);
|
||||
}
|
||||
}
|
||||
else if (tname == "script" || tname == "style")
|
||||
{
|
||||
|
|
|
@ -146,9 +146,16 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
|
|||
|
||||
return function(txt, cls)
|
||||
{
|
||||
|
||||
var disableAuthColorForThisLine = hooks.callAll("disableAuthorColorsForThisLine", {
|
||||
linestylefilter: linestylefilter,
|
||||
text: txt,
|
||||
"class": cls
|
||||
}, " ", " ", "");
|
||||
var disableAuthors = (disableAuthColorForThisLine==null||disableAuthColorForThisLine.length==0)?false:disableAuthColorForThisLine[0];
|
||||
while (txt.length > 0)
|
||||
{
|
||||
if (leftInAuthor <= 0)
|
||||
if (leftInAuthor <= 0 || disableAuthors)
|
||||
{
|
||||
// prevent infinite loop if something funny's going on
|
||||
return nextAfterAuthorColors(txt, cls);
|
||||
|
|
|
@ -50,6 +50,22 @@ var randomString = require('./pad_utils').randomString;
|
|||
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
|
||||
window.inInternationalComposition = false;
|
||||
var inInternationalComposition = window.inInternationalComposition;
|
||||
|
||||
window.handleCompositionEvent = function handleCompositionEvent(evt)
|
||||
{
|
||||
// international input events, fired in FF3, at least; allow e.g. Japanese input
|
||||
if (evt.type == "compositionstart")
|
||||
{
|
||||
this.inInternationalComposition = true;
|
||||
}
|
||||
else if (evt.type == "compositionend")
|
||||
{
|
||||
this.inInternationalComposition = false;
|
||||
}
|
||||
}
|
||||
|
||||
function createCookie(name, value, days, path)
|
||||
{
|
||||
if (days)
|
||||
|
|
|
@ -249,13 +249,13 @@ var padeditbar = (function()
|
|||
{
|
||||
var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/"));
|
||||
var readonlyLink = basePath + "/p/" + clientVars.readOnlyId;
|
||||
$('#embedinput').val("<iframe name='embed_readonly' src='" + readonlyLink + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>");
|
||||
$('#embedinput').val("<iframe name='embed_readonly' src='" + readonlyLink + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400></iframe>");
|
||||
$('#linkinput').val(readonlyLink);
|
||||
}
|
||||
else
|
||||
{
|
||||
var padurl = window.location.href.split("?")[0];
|
||||
$('#embedinput').val("<iframe name='embed_readwrite' src='" + padurl + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>");
|
||||
$('#embedinput').val("<iframe name='embed_readwrite' src='" + padurl + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400></iframe>");
|
||||
$('#linkinput').val(padurl);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue