I am using prosemirror to build a collaborative editor, where multiple people can edit one document. I wrote the following code, based on the example given here - http://prosemirror.net/docs/guides/collab/
Here is the code-
const { EditorState } = require('prosemirror-state');
const { EditorView } = require('prosemirror-view');
const { DOMParser } = require("prosemirror-model");
const {schema} = require("./schema");
var collab = require("prosemirror-collab");
function Authority(doc) {
this.doc = doc
this.steps = []
this.stepClientIDs = []
this.onNewSteps = []
}
Authority.prototype.receiveSteps = function(version, steps, clientID) {
if (version != this.steps.length) return
var self = this
// Apply and accumulate new steps
steps.forEach(function(step) {
self.doc = step.apply(self.doc).doc
self.steps.push(step)
self.stepClientIDs.push(clientID)
})
// Signal listeners
this.onNewSteps.forEach(function(f) { f() })
}
Authority.prototype.stepsSince = function(version) {
return {
steps: this.steps.slice(version),
clientIDs: this.stepClientIDs.slice(version)
}
}
var auth = new Authority('');
collabEditor(auth)
function collabEditor(authority) {
var view = new EditorView(document.querySelector("#editor"), {
state: EditorState.create({schema: schema, plugins: [collab.collab()]}),
dispatchTransaction: function(transaction) {
var newState = view.state.apply(transaction)
view.updateState(newState)
var sendable = collab.sendableSteps(newState)
if (sendable)
authority.receiveSteps(sendable.version, sendable.steps,
sendable.clientID)
}
})
authority.onNewSteps.push(function() {
var newData = authority.stepsSince(collab.getVersion(view.state))
view.dispatch(
collab.receiveTransaction(view.state, newData.steps, newData.clientIDs))
})
return view
}
When i run this code (after installing all the dependencies and setting up a simple server in nodejs) I am basically able to edit a text box but I am not able to open two tabs in chrome and see the collaboration happen. What am i doing wrong?
Will love some feedback.
This is the example code for a simple, single-page, no-external-communication setup. As such, no, it won't communicate to other tabs. For that, you'd have to move the authority somewhere else and set up pages to actually communicate with it over HTTP or websockets. (See for example this demo.)
Related
So I'm needing to get the list of file names from a range of Google Drive URLs in a spreadsheet. Browsing around the net, I came across the code below. It works but only for the old style urls, which I heard Google changed in September 2021.
Note that links are not fully functional, please replace with real links to check!
The old style is:
https://drive.google.com/file/d/1GMUwYxZxsNpLiaYOiVMBwl41LpreQ-fc/view?usp=sharing
This works correctly from the code below.
What I'd like though is two things.
It should handle a range of a couple of columns, currently reading AE2:AE, and printing out on AM2:AM. What I'd like is to go through the range: AE2:AL and print out: AM2:AT
Secondly it should also handle the newer form urls:
https://drive.google.com/file/d/0B9EZQqsLDEqDUGlsdy1oVEtETGs/view?usp=sharing&resourcekey=0-h7HOcxayPaHJ5r6dAAslVQ
Current Code:
function getNames() {
var activeRange = SpreadsheetApp.getActiveSheet().getDataRange();
var height = activeRange.getHeight();
var links = SpreadsheetApp.getActiveSheet()
.getRange("AE2:AE" + height)
.getValues();
var nameValues = [];
links.forEach((row) => {
try {
var link = row[0];
var fileID = getIdFromLink(link);
var name = DriveApp.getFileById(fileID).getName();
nameValues.push([name]);
} catch (e) {
nameValues.push(["NO NAME FOUND"]);
}
});
var nameRange = SpreadsheetApp.getActiveSheet().getRange("AM2:AM" + height);
nameRange.setValues(nameValues);
}
function getIdFromLink(link) {
var regex = new RegExp(
/(?<=https:\/\/drive\.google\.com\/file\/d\/)(.+)(?=\/)/
);
return regex.exec(link)[0];
}
How should the code above be modified to enable what I'm wanting. Sorry, I tried a couple of if/else statements, but my Javascript knowledge is severely limited.
Any help would be greatly appreciated.
Current "screenshot" showing:
(1) - Old style url - correctly picking up file name (2)
(3) - New style url - not picking up file name (4)
Your getIdFromLink() function should work just fine as long as the files have not been shared in such a way that they require a resource key as well.
To work with resource keys, use DriveApp.getFileByIdAndResourceKey(), like this:
function getFileNamesByLink() {
const sheet = SpreadsheetApp.getActiveSheet();
const sourceRange = sheet.getRange('AE2:AL');
const targetRange = sheet.getRange('AM2');
const fileNames = sourceRange.getValues()
.map(row => row.map(link => getFileNameFromLink_(link)));
targetRange
.offset(0, 0, fileNames.length, fileNames[0].length)
.setValues(fileNames);
}
function getFileNameFromLink_(link) {
if (!link) {
return null;
}
const fileId = getIdFromLink_(link);
if (!fileId) {
return NaN;
}
let file;
try {
file = DriveApp.getFileById(fileId);
} catch (error) {
try {
file = DriveApp.getFileByIdAndResourceKey(fileId, getResourceKeyFromLink_(link));
} catch (error) {
return NaN;
}
}
return file.getName();
}
function getIdFromLink_(link) {
const match = String(link).match(/file\/d\/([-\w]+)/i);
return match ? match[1] : null;
}
function getResourceKeyFromLink_(link) {
const match = String(link).match(/resourcekey=([-\w]+)/i);
return match ? match[1] : null;
}
Note that the script may time out if you have thousands of links. If that happens, process the links in a piecemeal fashion, or see if the Advanced Drive Service works for you.
(Small edit: "ce" is a shortcut for "React.createElement")
I have been working on a React/WebSockets/AJAX project for a chat room/message board. I am quite new to React, and I have caught on to most of it but I am having trouble with dynamically updating a list/refreshing its items.
What I want to do is every time my WebSocket receives an "update" message, I want to update the lists with the latest messages. The issue I am having is that they are not displaying anything, even though my update method is being called properly. I am getting no errors.
In my UserPageComponent, I have:
constructor(props) {
super(props);
this.state = {
channelType: "global",
messageToSend: "",
target: "",
globalMessages: [],
privateMessages: []
};
}
In my UserPageComponent render I have this:
... return 'Global Chat: ',
ce('ul', {id: "globalMessageDiv"}, this.state.globalMessages),
'Private Chat: ',
ce('ul', {id: "privateMessageDiv"}, this.state.privateMessages),
...
Here is my update (called every time a new message is sent - keep in mind globalMsgs/privateMsgs is populated properly with ALL messages sent as of when it was called).
updateData() {
const globalMsgs = this.getMessages("global");
const privateMsgs = this.getMessages("private");
var compiledGms = [];
var compiledPms = [];
globalMsgs.map((gm) => {
var gmToLi = ce('li', gm);
compiledGms.push(gmToLi);
});
privateMsgs.map((pm) => {
var pmToLi = ce('li', pm);
compiledPms.push(pmToLi);
});
this.setState({globalMessages: compiledGms});
this.setState({privateMessages: compiledPms});
}
The update function is called whenever I send a message and works like needed. (example below)
I'm unsure what else I can provide, however here is an example of what "globalMsgs" holds: data in globalMsgs/privateMsgs variables example
Try this below code
updateData() {
const globalMsgs = this.getMessages("global");
const privateMsgs = this.getMessages("private");
var compiledGms = [];
var compiledPms = [];
for(var i=0;i<globalMsgs.length;i++){
var gmToLi = ce('li', globalMsgs[i]);
compiledGms.push(gmToLi);
if(i==globalMsgs.length-1){
this.setState({globalMessages: compiledGms});
}
}
for(var i=0;i<privateMsgs.length;i++){
var pmToLi = ce('li', privateMsgs[i]);
compiledPms.push(pmToLi);
if(i==privateMsgs.length-1){
this.setState({privateMessages: compiledPms});
}
}
}
I need to create web browser using CefSharp.Wpf with ability to give fake data to site for example CPU cores, browser plugins, platform name etc.
There are site that can retrieve all this info: https://www.deviceinfo.me/
My quesiton is: How to hide GPU info from this site? Using javascript or CefSharp functionality
I have tried to redefine WebGLRenderingContext.getParameter method, which gives an info about GPU renderer and vendor:
var canvas = document.createElement('canvas');
var gl;
try {
gl = canvas.getContext("webgl2") || canvas.getContext("webgl") || canvas.getContext("experimental-webgl2") || canvas.getContext("experimental-webgl");
} catch (e) {
}
var oldParam = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter){
console.log("we have guests");
if(parameter == debugInfo.UNMASKED_RENDERER_WEBGL){
return "GTX 1080";
}
if(parameter == gl.getExtension("WEBGL_debug_renderer_info").UNMASKED_RENDERER_WEBGL){
return "GTX 1080";
}
if(parameter == debugInfo.UNMASKED_RENDERER_WEBGL){
return "NVidia";
}
if(parameter == gl.VERSION){
return "GTX 1080";
}
return oldParam(parameter);
};
I expected to completely redefine this method and return some fake info, but when i called gl.getParameter(param) again, it still gave me an old gpu info
If you still want Canvas2D and WebGL to still work then you can't hide since they can finger print by actually rendering.
You could disable them with
HTMLCanvasElement.prototype.getContext = function() {
return null;
};
Though the fact they don't exist is also a data point.
Otherwise your wrapper appears to have some issues.
First you really should set the function before creating the context.
Second your last line should be
oldParam.call(this, parameter);
Also you didn't show debugInfo but you can use WebGLRenderingContext instead or you can just hard code the numbers
As for http://www.deviceinfo.me you need to make sure your patch runs in all iframes and workers before any other JavaScript.
WebGLRenderingContext.prototype.getParameter = function(origFn) {
const paramMap = {};
paramMap[0x9245] = "Foo"; // UNMASKED_VENDOR_WEBGL
paramMap[0x9246] = "Bar"; // UNMASKED_RENDERER_WEBGL
paramMap[0x1F00] = "Nobody"; // VENDOR
paramMap[0x1F01] = "Jim"; // RENDERER
paramMap[0x1F02] = "Version 1.0"; // VERSION
return function(parameter) {
return paramMap[parameter] || origFn.call(this, parameter);
};
}(WebGLRenderingContext.prototype.getParameter);
// --- test
const gl = document.createElement('canvas').getContext('webgl');
const ext = gl.getExtension('WEBGL_debug_renderer_info');
show(gl, gl, [
'VENDOR',
'RENDERER',
'VERSION',
]);
if (ext) {
show(gl, ext, [
'UNMASKED_VENDOR_WEBGL',
'UNMASKED_RENDERER_WEBGL',
]);
}
function show(gl, base, params) {
for (const param of params) {
console.log(param, ':', gl.getParameter(base[param]));
}
}
There is WebGLRenderingContext and WebGL2RenderingContext
I have a function prototype that loads data from a path. The trick is that I need to change the path afterward. I tried call, apply, bind and even assign but as I am a novice I did not find the solution.
Here a sample of my code :
Chat.prototype.loadMessages = function() {
this.messagesRef = this.database;
var setMessage = function(data) {
var val = data.val();
this.displayMessage(data.key, val.name, val.text);
}.bind(this);
};
var chat = new Chat
function setPath (newpath) {
chat.loadMessages.messageRef = newpath; // I guess, it is where I'm wrong...
chat.loadMessages(); // It should load messages from the new path in my chat container.
}
As I said I also tried :
chat.loadMessages.call(newpath);
or
var setPath = function(newpath) {
chat.loadMessages(newpath);
}.bind(chat);
setPath();
chat.loadMessages();
But the chat container continues to disclose messages from the old path...
This looks a bit convoluted. Just pass messagesRef as a parameter and make it default to this.database:
Chat.prototype.loadMessages = function(messagesRef = this.database) {
// do whatever is needed with messagesRef
};
chat = new Chat();
chat.loadMessages(); // load from the default location
chat.loadMessages('foobar'); // load from this specific location
It looks like you are creating a function with loadMessages, which is fine but you need to pass in a value to set the new path. Is this more of what you were thinking?
Chat.prototype.loadMessages = function (newPath) {
this.messagesRef = newPath || this.database; // if newPath is empty than default to this.database
var setMessage = function(data) {
var val = data.val();
this.displayMessage(data.key, val.name, val.text);
};
var chat = new Chat
function setPath (newpath) {
chat.loadMessages(newpath);
}
I'm using a proxy class as the data I have is a reference to a Firebase location that stores my object but I want to act as if I have the object itself. I've got something that works fine but I would like to improve it, the key criteria being to reduce repetition. I suspect something is possible by inspecting the Map class and using apply() but I don't know quite how to do that (or if there is a better solution).
I think it would also be useful if the solution could be generalised to support any class, not just the Map class.
var Map = function() {
...
};
var MapProxy = function(mapRef) {
this.mapRef = mapRef;
};
Map.prototype.addToken = function(portrait, newLocation) {
...
};
Map.prototype.removeToken = function(token) {
...
};
Map.prototype.moveToken = function(token, newLocation) {
...
};
MapProxy.prototype.addToken = function(portrait, newLocation) {
var mapRef = this.mapRef;
mapRef.once('value', function(data) {
var map = new Map();
map.init(mapRef, data.val());
map.addToken(portrait, newLocation);
});
};
MapProxy.prototype.removeToken = function(token) {
var mapRef = this.mapRef;
mapRef.once('value', function(data) {
var map = new Map();
map.init(mapRef, data.val());
map.removeToken(token);
});
};
MapProxy.prototype.moveToken = function(token, newLocation) {
var mapRef = this.mapRef;
mapRef.once('value', function(data) {
var map = new Map();
map.init(mapRef, data.val());
map.moveToken(token, newLocation);
});
};
var mapProxy = new MapProxy(mapRef);
Think I solved it myself in the end.
var FirebaseProxy = function(classToProxy, firebaseRef) {
var key,
self = this;
self.proxy = classToProxy;
self.firebaseRef = firebaseRef;
for (key in self.proxy.prototype) {
if (typeof self.proxy.prototype[key] === 'function') {
(function(inner_key) {
self[inner_key] = function ()
{
var args = arguments;
self.firebaseRef.once('value', function(data) {
var proxiedInstance = new self.proxy();
if (typeof proxiedInstance.init === 'function') {
proxiedInstance.init(self.firebaseRef, data.val());
}
proxiedInstance[inner_key].apply(proxiedInstance, args);
});
}
})(key);
}
}
}
I don't think I completely follow what you're trying to accomplish. Could you forego the proxy and just use something like this?
var Map = function(mapRef) {
mapRef.on('value', function(snap) {
this.init(snap.val());
});
};
Map.prototype.init = function(data) {
// update internal state with new data from Firebase ...
};
...
Since 'value' will fire every time the data at mapRef changes, your map object will always have the latest data.
It's worth noting that if you're going to be needing the latest map data on a regular basis, you should probably use .on(), not .once(). .once() will go and retrieve the data from the servers every time you ask for it, while .on() will always have the latest data cached (since it subscribes to updates). So it'll be faster and use less bandwidth.