As documented, TinyMCE allows uploading images automatically: one defines an endpoint (images_upload_url), which is expected to upload the image and return the location for TinyMCE to use in markup.
However, when pasting, dragging or inserting an image from a URL — for example, https://somecdn.com/image.png — TinyMCE will simply embed the image tag with the somecdn.com source, instead of sending the URL to images_upload_url to be uploaded.
I've scoured through the docs here and haven't found any way to configure TinyMCE to do this. Is there a method I can override in order to upload images from URLs as well as local image uploads?
Summary
To clarify:
Current behavior with local image dragged in: TinyMCE sends the image to the URL specified in images_upload_url, then embeds the source returned.
Current behavior with remote image dragged in: TinyMCE embeds the remote image, sourced with its remote URL.
Desired behavior with remote image dragged in, similar to well established products like Microsoft's GroupMe: TinyMCE sends the image URL to the URL specified in images_upload_url, then embeds the source returned. I can figure out how to upload the URL & manually embed the image in TinyMCE, but I need to know what event to intercept to get the dragged-in image URL!
With some tinkering, I was able to figure out how to handle remote images in TinyMCE!
As I'm using TinyMCE with React, I added an onDrop event:
<Editor
onDrop={onDrop}
And here is my method implementation, with comments:
const onDrop = useCallback((e) => {
const image_url = e.dataTransfer.getData("URL");
/*
If this is a local file, use the default functionality
provided using images_upload_url
*/
if (!image_url) {
return;
}
/*
Otherwise, intercept the drop event, get the file URL,
send it to the API to be uploaded, then embed in content
*/
e.preventDefault();
filesAPI
.upload({
image_url,
})
.then((response) => {
const { location } = response.data;
editorRef.current.execCommand(
"mceInsertContent",
false,
`<img src='${location}' />`
);
});
return false;
}, []);
The implementation seems to work pretty well! When local images are dropped in, they're processed by the default handler; otherwise the onDrop method uploads the images & inserts a tag with their location. Note that filesAPI in this context is just my app's wrapper around an axios fetch call.
What you are wanting to do is not technically possible for a great many "remote" images as most sites won't have the appropriate CORS headers in place to allow for what you want to happen. You can fetch/proxy the image via some server side code and then do whatever you want with it once it is fetched server side.
All that being said you also enter a slippery slope of "taking" someone else's intellectual property and storing/serving it from your server which in many cases would be against copyright laws.
EDIT (based on your comments):
The images_upload_url feature is not designed to do what you want it to do. It is not that you cannot do what you are describing but images_upload_url won't do that for you. You can try to use some of TinyMCE's events to capture content insertion and trigger that sort of behavior or you could simply wait for the content to be saved to the server and perform that processing when the content is prepared for saving to your data store.
There are a whole host of events that TinyMCE provides along with some of the standard browser events:
https://www.tiny.cloud/docs/advanced/events/#handlingeditorevents
Related
I have found weird bug in my application. I am using VueJS3 and typescript. I am creating a media gallery, where users can manage assets. All endpoints and backend side of application works perfectly. Currently i am working on updating image files(updating image field). My code works in theory, image url updates to a new one, but even the url is newer, image that is rendered is old image(before update). When i try to view my application in incognito window, my newer image is showing. But it does not work otherwise(works if and only user clears his cache) I hope i explained myself clearly, it is a little complicated :)
When i fetch the data from api, they don't have any src, so i have to create a property when using the data. Here is the code below.
const { res, err } = await this.$api.image.getAll(params)
res.data.items.forEach(async (image: Image<Populated>) => {
image.src = this.$fileHandler(image._id)
})
Note: File handler is a special method that gives the image source by id
Apparently your assets get cached, and there are multiple ways to solve that.
Not diving too much into caching strategies and cache control tags, just do
image.src = `${this.$fileHandler(image._id)}?_=${+new Date()}`
This will generate a unique (almost) URL for your image, invalidating every previous cached one.
I show images uploaded on another platform on my site, but I want to protect my users from downloading extremely large files (50MB gifs for example). I dont have any control over the upload process and cant limit files during upload.
Is there any option to limit the file size of an image on a webpage?
Something like <img src="..." maxSize="5MB" /> and the browser would cancel the download if it detects that the file is bigger than 5MB ?
(This answer comes with the assumption that you have permission from the other platform to use images. This kind of activity is fraught with difficulty over things like copyright. Caveat venditor.)
Use a call to your own site as a proxy
Rather than hotlinking to the remote site directly from HTML, send requests to your own server.
From there, stream the image from the remote source. If you detect that the image is too large, you can produce an error response.
Even assuming you have permission to use images from the remote site, be sure to set the proper cache headers. You might also want to store a copy of the image on your own server.
Before rendering the images on your site, you could perform a HEAD-request and inspect the Content-Length header of the response.
HEAD
The HTTP HEAD method requests the headers that would be returned if the HEAD request's URL was instead requested with the HTTP GET method. For example, if a URL might produce a large download, a HEAD request could read its Content-Length header to check the filesize without actually downloading the file.
Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD
As an example, you could use the fetch api:
// This URL should point to a very large image, but currently it points to an avatar
const imageUrl = 'https://www.gravatar.com/avatar/eb03f51fd573efda46453d9b928de5e2?s=48&d=identicon&r=PG';
// Example
fetch(imageUrl, { method: 'HEAD' })
.then(response => {
const imageSizeInBytes = response.headers.get('content-length');
console.log(`Image size: ${imageSizeInBytes} bytes.`);
});
I'm leaving my moving from a flash based image editor for our custom CMS to the last minute I know, but PhotoeditorSDK seems to be the thing we need. However my Javascript programming is not up to much so I'm struggling with how to deal with the resultant image I want to export.
I can upload and pass the file to the editor no problem.
I just want to post the resulting processed image to my file handling (which is CFML Lucee) by passing it a file or url or form field - doesnt matter which really.
But the documentation on the SDK only appears to be limited to this (in the export documentation)
editor.on(UIEvent.EXPORT, async (image) => {
// todo: handle exported image here
So I'm stuck.
What I would like to happen is to have the resulting image (post editing) sent to my script, where I can do what I need to to on solid ground.
Any suggestions or areas to explore greatfully received
A lot of these editors export DIRECTLY from the browser using SVG or other techniques, so chances are that is what they are doing.
to export or save image simply just redirect to the url of the download which will download the file without navigating the page. you can add any params you want to the get request including the base64 of image
document.location='./imageout.cfm?content='+image; (or whatever url generates the exported doc and myimageref is the variable with a reference or path of what to export.
Any docs url we can look at?
otherwise you can just create a form object in js and submit the form with image as a field with POST.
I am implementing the Adobe Creative SDK product onto my site for administrative use; administrators are able to access specific images (used on the frontpage slider), edit, and save.
The trouble is that Adobe's documentation on how to take advantage of the onSave() callback function is very vague. I had to go to the old site, Aviary, to find answers, but even there it's quite vague.
First, I am pulling the images off the server using a MySql DB query (there's at least 2 images in the slider so I wanted this to be database-driven rather than static). The images are stored as files with reference to them in the DB.
Second, once the images are displayed on the page (all of them are displayed on the administrative page along with text overlays, links, etc.), the administrator can click on the image and the Adobe Creative SDK is called and the editor window shows. All good.
Third, after editing, the admin can click "Save" and his edits are saved to the Adobe cloud temporarily (and the edited image replaces the original image on the page). What I need is for the image to ALSO save on my server OVERRIDING the original image (I don't want to have to do a DB update - too much extra work).
This is where the vague instructions at Adobe and Aviary are not helpful.
Here's my code...
(These is the functions from Adobe Creative SDK):
var featherEditor = new Aviary.Feather({
apiKey: 'myapikey',
theme: 'dark', // Check out our new 'light' and 'dark' themes!
tools: 'all',
appendTo: '',
onSave: function(imageID, newURL) {
var img = document.getElementById(imageID);
img.src = newURL;
},
onError: function(errorObj) {
alert(errorObj.message);
}
});
function launchEditor(id, src) {
featherEditor.launch({
image: id,
url: src
});
return false;
}
Essentially each image that is loaded includes in the <img> tag an onclick event which looks like:
<img id="editableimage<?php echo $srow->id ?>" src="assets/slider/<?php echo $srow->sld_image ?>" />
This calls the launchEditor function and shows the editor. When Save is clicked, among a few other things, the onSave() callback is fired and it is in that callback function that I can save the image locally.
BUT Adobe only offers the following examples to accomplish this and they make little sense to me:
First, it appears that this needs to be added to the onSave() function:
$.post('/save', {url: newURL, postdata: 'some reference to the original image'})
I'm assuming that the '/save' would actually be the php script I use to do the work...or maybe it's the location on the server to save the image...not sure. The 'postdata' says it needs "some reference to the original image", but I don't really know how to get that. I tried using "url" from the launchEditor() function because it appears that it was passed to the featherEditor, but that didn't work, it just returned a null value when I did an alert().
If someone could help me figure this out, I can easily take care of the server side php which does the saving. But I just don't know how to get the new image that Adobe has saved to override the old image on my server. Thanks!
The Image Editor onSave() method
onSave() is just a hook; it is the method called after the image save is complete. What you put inside of the onSave() method is entirely up to you.
Just to illustrate, you could 1) replace the original image element's source with the new edited image URL, then 2) close the editor:
onSave: function(imageID, newURL) {
originalImage.src = newURL;
featherEditor.close();
}
You could even just put a console log in there, but that wouldn't do much for the user.
Again, onSave() is just a hook that is used after the save is complete, and its content is completely up to you.
Posting to your server
The code you showed in your question is just an example of how you might post the data to your server using jQuery within the Image Editor's onSave() method. There is no requirement that you do it this way; you don't even have to use jQuery.
For clarity, here's a look at that example again:
$.post('/save', {url: newURL, postdata: 'some reference to the original image'})
The endpoint
The example above uses the jQuery post() method to hit a /save endpoint on your server. This endpoint could be anything you want it to be. If you have an endpoint on your server called /upload-image, you could use that instead.
In your case, at this endpoint would be the PHP script that handles the image file save and database update.
The post data
The second argument to post() is an object with the data that you want to pass. In this example, we're passing:
the newURL of the edited image so your server can do something with it (see Important note below)
a reference to the original image (arbitrarily named postdata here) so your server can know what image was edited
You can put anything you want in this object. It depends on what your server script needs. But at the bare minimum, I would think it would need the newURL and likely some way to reference the original image in your database.
Important note: as noted in the Creative SDK for web Image Editor guide, the newURL that you receive in the onSave() method is a temporary URL. It expires in 72 hours. That means that your server script needs to save the image itself to your server. If you only store the URL in your database, your images will start disappearing in 72 hours.
I'm trying to write a Google Chrome extension for showing PDF files. As soon as I detect that browser is redirecting to a URL pointing to a PDF file, I want it to stop loading the default PDF viewer, but start showing my UI instead. The UI will use PDF.JS to render the PDF and jQuery-ui to show some other stuff.
Question: how do I make this? It's very important to block the original PDF viewer, because I don't want to double memory consumption by showing two instance of the document. Therefore, I should somehow navigate the tab to my own view.
As the main author of the PDF.js Chrome extension, I can share some insights about the logic behind building a PDF Viewer extension for Chrome.
How to detect a PDF file?
In a perfect world, every website would serve PDF files with the standard application/pdf MIME-type. Unfortunately, the real world is not perfect, and in practice there are many websites which use an incorrect MIME-type. You will catch the majority of the cases by selecting requests that satisfy any of the following conditions:
The resource is served with the Content-Type: application/pdf response header.
The resource is served with the Content-Type: application/octet-stream response header, and its URL contains ".pdf" (case-insensitive).
Besides that, you also have to detect whether the user wants to view the PDF file or download the PDF file. If you don't care about the distinction, it's easy: Just intercept the request if it matches any of the previous conditions.
Otherwise (and this is the approach I've taken), you need to check whether the Content-Disposition response header exists and its value starts with "attachment".
If you want to support PDF downloads (e.g. via your UI), then you need to add the Content-Disposition: attachment response header. If the header already exists, then you have to replace the existing disposition type (e.g. inline) with "attachment". Don't bother with trying to parse the full header value, just strip the first part up to the first semicolon, then put "attachment" in front of it. (If you really want to parse the header, read RFC 2616 (section 19.5.1) and RFC 6266).
Which Chrome (extension) APIs should I use to intercept PDF files?
The chrome.webRequest API can be used to intercept and redirect requests. With the following logic, you can intercept and redirect PDFs to your custom viewer that requests the PDF file from the given URL.
chrome.webRequest.onHeadersReceived.addListener(function(details) {
if (/* TODO: Detect if it is not a PDF file*/)
return; // Nope, not a PDF file. Ignore this request.
var viewerUrl = chrome.extension.getURL('viewer.html') +
'?file=' + encodeURIComponent(details.url);
return { redirectUrl: viewerUrl };
}, {
urls: ["<all_urls>"],
types: ["main_frame", "sub_frame"]
}, ["responseHeaders", "blocking"]);
(see https://github.com/mozilla/pdf.js/blob/master/extensions/chromium/pdfHandler.js for the actual implementation of the PDF detection using the logic described at the top of this answer)
With the above code, you can intercept any PDF file on http and https URLs.
If you want to view PDF files from the local filesystem and/or ftp, then you need to use the chrome.webRequest.onBeforeRequest event instead of onHeadersReceived. Fortunately, you can assume that if the file ends with ".pdf", then the resource is most likely a PDF file. Users who want to use the extension to view a local PDF file have to explicitly allow this at the extension settings page though.
On Chrome OS, use the chrome.fileBrowserHandler API to register your extension as a PDF Viewer (https://github.com/mozilla/pdf.js/blob/master/extensions/chromium/pdfHandler-vcros.js).
The methods based on the webRequest API only work for PDFs in top-level documents and frames, not for PDFs embedded via <object> and <embed>. Although they are rare, I still wanted to support them, so I came up with an unconventional method to detect and load the PDF viewer in these contexts. The implementation can be viewed at https://github.com/mozilla/pdf.js/pull/4549/files. This method relies on the fact that when an element is put in the document, it eventually have to be rendered. When it is rendered, CSS styles get applied. When I declare an animation for the embed/object elements in the CSS, animation events will be triggered. These events bubble up in the document. I can then add a listener for this event, and replace the content of the object/embed element with an iframe that loads my PDF Viewer.
There are several ways to replace an element or content, but I've used Shadow DOM to change the displayed content without affecting the DOM in the page.
Limitations and notes
The method described here has a few limitations:
The PDF file is requested at least two times from the server: First a usual request to get the headers, which gets aborted when the extension redirects to the PDF Viewer. Then another request to request the actual data.
Consequently, if a file is valid only once, then the PDF cannot be displayed (the first request invalidates the URL and the second request fails).
This method only works for GET requests. There is no public API to directly get response bodies from a request in a Chrome extension (crbug.com/104058).
The method to get PDFs to work for <object> and <embed> elements requires a script to run on every page. I've profiled the code and found that the impact on performance is negligible, but you still need to be careful if you want to change the logic.
(I first tried to use Mutation Observers for detection, which slowed down the page load by 3-20% on huge documents, and caused an additional 1.5 GB peak in memory usage in a complex DOM benchmark).
The method to detect <object> / <embed> tags might still cause any NPAPI/PPAPI-based PDF plugins to load, because it only replaced the <embed>/<object> tag's content when it has already been inserted and rendered. When a tab is inactive, animations are not scheduled, and hence the dispatch of the animation event will significantly be delayed.
Afterword
PDF.js is open-source, you can view the code for the Chrome extension at https://github.com/mozilla/pdf.js/tree/master/extensions/chromium. If you browse the source, you'll notice that the code is a bit more complex than I explained here. That's because extensions cannot redirect requests at the onHeadersReceived event until I implemented it a few months ago (crbug.com/280464, Chrome 35).
And there is also some logic to make the URL in the omnibox look a bit better.
The PDF.js extension continues to evolve, so unless you want to significantly change the UI of the PDF Viewer, I suggest to ask users to install the PDF.js's official PDF Viewer in the Chrome Web Store, and/or open issues on PDF.js's issue tracker for reasonable feature requests.
Custom PDF Viewer
Basically, to accomplish what you want to do you'll need to:
Interject the PDF's URL when it's loaded;
Stop the PDF from loading;
Start your own PDF viewer and load the PDF inside it.
How to
Using the chrome.webRequest API you can easily listen to the web requests made by Chrome, and, more specifically, the ones that are going to load .pdf files. Using the chrome.webRequest.onBeforeRequest event you can listen to all the requests that end with ".pdf" and get the URL of the requested resource.
Create a page, for example display_pdf.html where you will show the PDFs and do whatever you want with them.
In the chrome.webRequest.onBeforeRequest listener, prevent the resource from being loaded returning {redirectUrl: ...} to redirect to your display_pdf.html page.
Pass the PDF's URL to your page. This can be done in several ways, but, for me, the simplest one is to add the encoded PDF URL at the end of your page's url, like an encoded query string, something like display_pdf.html?url=http%3A%2F%2Fwww.example.com%2Fexample.pdf.
Inside the page, get the URL with JavaScript and process and render the PDF with any library you want, like PDF.js.
The code
Following the above steps, your extension will look like this:
<root>/
/background.js
/display_pdf.html
/display_pdf.js
/manifest.json
So, first of all, let's look at the manifest.json file: you will need to declare the permissions for webRequest and webRequestBlocking, so it should look like this:
{
"manifest_version": 2,
"name": "PDF Test",
"version": "0.0.1",
"background": {
"scripts": ["/background.js"]
},
"permissions": ["webRequest", "webRequestBlocking", "<all_urls>"],
}
Then, in your background.js you will listen to the chrome.webRequest.onBeforeRequest event and update the tab which is loading the PDF with the URL of your custom display_pdv.html page, like this:
chrome.webRequest.onBeforeRequest.addListener(function(details) {
var displayURL;
if (/\.pdf$/i.test(details.url)) { // if the resource is a PDF file ends with ".pdf"
displayURL = chrome.runtime.getURL('/display_pdf.html') + '?url=' + encodeURIComponent(details.url);
return {redirectUrl: displayURL};
// stop the request and proceed to your custom display page
}
}, {urls: ['*://*/*.pdf']}, ['blocking']);
And finally, in your display_pdf.js file you will extract the PDF's url from the query string and use it to do whatever you want:
var PDF_URL = decodeURIComponent(location.href.split('?url=')[1]);
// this will be something like http://www.somesite.com/path/to/example.pdf
alert('The PDF url is: ' + PDF_URL);
// do something with the pdf... like processing it with PDF.js
Working Example
A working example of what I said above can be found HERE.
Documentation links
I recommend you to take a look at the official documentation of the above specified APIs, that you can find following these links:
chrome.webRequest API
chrome.webRequest.onBeforeRequest event
chrome.runtime API
chrome.runtime.getURL method