I am opening this to point at #4555.
I believe the problem is not solved and should be reopened.
All details are in my last comment on that thread.
Completely agree. The preview links should be fully filterable with the preview_post_link
filter. I'm not sure why that went away, or why the "autosaves" solution was considered sufficient.
Hi there, I'll close this and reopen #4555 and we can continue the discussion there.
Reopened the issue for @danielbachhuber and @yoadsn, see #4555
@danielbachhuber Recap of the problem analysis as described on #4555.
As noted - preview_post_link
is useful and should be backward compatible using Gutenberg.
I believe the fix in https://core.trac.wordpress.org/ticket/44180 is not complete.
Here is the scenario I am facing and my analysis.
WP admin is accessed and used from domain A
WP frontend is on domain B using reverse proxy - this is very common when wanting to host WP on a sub folder of a TLD
(But this senario I believe applies also to any case where home_url !== site_url or in fact anytime the preview url needs to be filterable)
Using Gutenberg for a draft post.
After an autosave - the autosave endpoint return a preview_url
as you have mentioned above.
The reducer here would take the preview_link
as is and use that for the preview button.
As you have shown on the screenshots of the PR #6682 - hovering over the "preview" button at this point would show the url as it was returned from the autosave endpoint.
This URL was generated here and using the get_preview_post_link
which makes the preview link filterable.
If now the "preview" button is clicked or the "save" link is clicked. The clien would hit the posts controller and get the payload which includes the post's link
.
This link is generated here and is using the get_permalink
function.
The permalink is filterable but in my scenario preview link need to work on site_url
and not the home_url
- permalinks should always be on home_url
. (for the preview to work, all admin auth is against site_url
).
The reason this works is that the reducer mentioned above applies the { preview: true }
query param on the client.
This means there is an inconsistent behaviour between the way preview links are generated form the autosave controller and on the client.
Anyway - hovering over the button would now show a different URL (assuming site_url
!== home_url
).
I hope I analyzed the situation correctly - I'm jst a user of WP so first time looking at the source codes.
I might be able to work around this problem if I will filter the permalink and somehow know that it is going to be used on the "editor" and not on the frontend - but this is not possible since the posts controller does not identify itself to the call in anyway.
Thoughts?
That's a really painful bug, and suffer from a similar use case described by @yoadsn .
We use a reverse proxy to serve our legacy app and wordpress on the same domain. We need a specific url starting with a prefix that can be used by AWS Cloudfront to select the proper backend.
I'm really looking forward a solution.
@yoadsn Sorry for the late reply here. Your analysis seems reasonable although I'm not quite sure what the fix should be.
@danielbachhuber Of course I may be talking none-sense here but I would keep the preview link generation logic on the backend as much as possible and ensure it uses standard filterable generation methods like get_preview_post_link
.
So, when hitting the "posts" controller - perhaps there will be a way to also ask for the "preview link" on top of the "link" (=permalink). This way, the client reducer can use that returned value instead of generating a preview link indirectly from the permalink.
I know the request can contain a fields
parameter but I'm unsure how easy it is to extend the schema for a post to include such optional field.
So in the controller - similar to this field data prep:
if ( in_array( 'link', $fields, true ) ) {
$data['link'] = get_permalink( $post->ID );
}
There will be: (Excuse my lack of source knowledge here - waving hands)
if ( in_array( 'preview_link', $fields, true ) ) {
$parent_id = wp_is_post_autosave( $post );
$preview_post_id = false === $parent_id ? $post->ID : $parent_id;
$preview_query_args = array();
if ( false !== $parent_id ) {
$preview_query_args['preview_id'] = $parent_id;
$preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
}
$data['preview_link'] = get_preview_post_link( $preview_post_id , $preview_query_args );
}
Quite similar to how the autosaves controller does it.
Hopefully this makes sense - reimplementing the link filtering on the client just sounds crazy to me and a bad call in terms of business logic encapsulation.
I would keep the preview link generation logic on the backend as much as possible and ensure it uses standard filterable generation methods like
get_preview_post_link
.
I agree with this.
So, when hitting the "posts" controller - perhaps there will be a way to also ask for the "preview link" on top of the "link" (=permalink). This way, the client reducer can use that returned value instead of generating a preview link indirectly from the permalink.
Right. The challenge is that I'm not sure, conceptually, how well this fits with the Posts Controller. Currently, the preview_link
is the responsibility of the Autosaves Controller, which makes more conceptual sense — you're _previewing_ a work-in-progress version of the post. At first glance, it doesn't seem like this fits cleanly with the Posts Controller.
But, maybe the simplest thing to do is simply add preview_link
to the Posts Controller and be done with it.
@aduth or @azaozz have spent a ton more time in this area and might have other thoughts.
Makes sense.
My 2 cents - Conceptually this API divides responsibility between "AutoSave" and "Save".
Although differences do exist, the use case is the same - modifying post state.
So if I would think this through I think I arrive at a conclusion that "Auto" is in fact a client concern and not a server concern. The Server API should have never indicate any difference between the two but rather facilitate either state mutations (Before publish and after publish) regardless of "auto" having . any meaning.
Given this is how the API is architected - Probably best to extend the post controller then overload the "autosave" controller with generic "saving" and "loading" of documents in any state.
As a bonus, the server can decide a "preview link" to a published version is just "the link" without the client caring.
There's a fair bit more context to the decision of creating a separate autosaves endpoint at:
https://core.trac.wordpress.org/ticket/43316#comment:62
...including considerations around revisions and the capabilities of the server in handling fields therein.
In Gutenberg, it doesn't really make much a difference whether it be one or separate endpoints, as long as we could communicate intent of the nature of the save as being of this more transient sort (i.e. not necessarily needing a full revision history).
Related: https://github.com/WordPress/gutenberg/issues/9151#issuecomment-414811399
As far as actionability: It seems there's not much reluctance about including preview_link
in the return response of a "full" post save (i.e. on the posts controller). If I recall correctly, this had also been the behavior in earlier implementations (#6882). May I suggest a next step be to propose this for consideration in an upcoming REST API meeting and/or create a Trac ticket to introduce this new field?
As far as I can tell, there would actually be no change required in Gutenberg for this to start being leveraged, since the current behavior is to use preview_link
from a response when and if it's available:
Hello,
I'm reading this thread and not sure what the next steps are. I rewrite preview links from: /?p=8792&preview=true
to /previews/?p=8792&preview=true
. For draft posts, this works from the posts index, but while editing, my hook for preview_post_link
never gets called. Is there a way to do this?
To add the preview_link
to the REST response you can use this snippet:
/**
* Includes preview link in post data for a response.
*
* @param \WP_REST_Response $response The response object.
* @param \WP_Post $post Post object.
* @return \WP_REST_Response The response object.
*/
function my_include_preview_link_in_rest_response( $response, $post ) {
if ( 'draft' === $post->post_status ) {
$response->data['preview_link'] = get_preview_post_link( $post );
}
return $response;
}
add_filter( 'rest_prepare_post', 'my_include_preview_link_in_rest_response', 10, 2 );
add_filter( 'rest_prepare_page', 'my_include_preview_link_in_rest_response', 10, 2 );
See also https://core.trac.wordpress.org/ticket/44180 for a similar version.
As far as I can tell, there would actually be no change required in Gutenberg for this to start being leveraged, since the current behavior is to use
preview_link
from a response when and if it's available:
@aduth You're correct. The only thing is that REQUEST_POST_UPDATE_SUCCESS
isn't called for the initial editor load when the data is preloaded. Wondering if we should change that.
There's still no filter to change the Preview link that is on the right side of Gutenberg.
Is there any news on this at all? :)
I'm having the same problem and """solved""" it with a workaround (notice the triple double quotes). Maybe it could help some people until there's an official solution. Feel free to use and/or improve this workaround.
Add this in your theme's functions.php
// workaround script until there's an official solution for https://github.com/WordPress/gutenberg/issues/13998
function fix_preview_link_on_draft() {
echo '<script type="text/javascript">
jQuery(document).ready(function () {
const checkPreviewInterval = setInterval(checkPreview, 1000);
function checkPreview() {
const editorPreviewButton = jQuery(".editor-post-preview");
const editorPostSaveDraft = jQuery(".editor-post-save-draft");
if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) {
editorPreviewButton.attr("href", "' . get_preview_post_link() . '");
editorPreviewButton.off();
editorPreviewButton.click(false);
editorPreviewButton.on("click", function() {
editorPostSaveDraft.click();
setTimeout(function() {
const win = window.open("' . get_preview_post_link() . '", "_blank");
if (win) {
win.focus();
}
}, 1000);
});
}
}
});
</script>';
}
add_action('admin_footer', 'fix_preview_link_on_draft');
The script is running every second as Gutenberg also updates the preview URL on certain actions. Each interval it will execute the following logic:
.editor-post-preview
and .editor-post-save-draft
and that the href is not the same as get_preview_post_link()
get_preview_post_link()
get_preview_post_link()
in a new tab on clickUse at your own risk! It might break on future Gutenberg versions
Tested on Wordpress 5.3.2
any news on this issue?
// workaround script until there's an official solution for https://github.com/WordPress/gutenberg/issues/13998 function fix_preview_link_on_draft() { echo '<script type="text/javascript"> jQuery(document).ready(function () { const checkPreviewInterval = setInterval(checkPreview, 1000); function checkPreview() { const editorPreviewButton = jQuery(".editor-post-preview"); const editorPostSaveDraft = jQuery(".editor-post-save-draft"); if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) { editorPreviewButton.attr("href", "' . get_preview_post_link() . '"); editorPreviewButton.off(); editorPreviewButton.click(false); editorPreviewButton.on("click", function() { editorPostSaveDraft.click(); setTimeout(function() { const win = window.open("' . get_preview_post_link() . '", "_blank"); if (win) { win.focus(); } }, 1000); }); } } }); </script>'; } add_action('admin_footer', 'fix_preview_link_on_draft');
Use at your own risk! It might break on future Gutenberg versionsTested on Wordpress 5.3.2
thanks @tvanro , your solution is the only one that has worked for me so far.
Thanks @tvanro that got me out of a pickle
I updated the code so it works with new posts and set an interval when clicked so that the window opens when the post is saved. I also moved the check for the draft button inside the click function because it may not exist on first visit with no updates. So this gives the user the ability to click preview when first visiting the edit screen for the post.
jQuery(document).ready(function () {
const checkPreviewInterval = setInterval(checkPreview, 1000);
function checkPreview() {
const editorPreviewButton = jQuery(".editor-post-preview");
if (editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) {
editorPreviewButton.attr("href", "' . get_preview_post_link() . '");
editorPreviewButton.off();
editorPreviewButton.click(false);
editorPreviewButton.on("click", function(e) {
const editorPostSaveDraft = jQuery(".editor-post-save-draft");
if(editorPostSaveDraft.length > 0) {
editorPostSaveDraft.click();
}
const intervalId = setInterval(function() {
// find out when the post is saved
let saved = document.querySelector(".is-saved");
if(saved) {
clearInterval(intervalId);
const win = window.open("' . get_preview_post_link() . '", "_blank");
if (win) {
win.focus();
}
}
}, 50);
});
}
}
});
Thanks @tvanro. Save me a lot of time.
We are still waiting for native solution of the isue..
I'm having the same problem and """solved""" it with a workaround (notice the triple double quotes). Maybe it could help some people until there's an official solution. Feel free to use and/or improve this workaround.
Add this in your theme's functions.php
// workaround script until there's an official solution for https://github.com/WordPress/gutenberg/issues/13998 function fix_preview_link_on_draft() { echo '<script type="text/javascript"> jQuery(document).ready(function () { const checkPreviewInterval = setInterval(checkPreview, 1000); function checkPreview() { const editorPreviewButton = jQuery(".editor-post-preview"); const editorPostSaveDraft = jQuery(".editor-post-save-draft"); if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) { editorPreviewButton.attr("href", "' . get_preview_post_link() . '"); editorPreviewButton.off(); editorPreviewButton.click(false); editorPreviewButton.on("click", function() { editorPostSaveDraft.click(); setTimeout(function() { const win = window.open("' . get_preview_post_link() . '", "_blank"); if (win) { win.focus(); } }, 1000); }); } } }); </script>'; } add_action('admin_footer', 'fix_preview_link_on_draft');
The script is running every second as Gutenberg also updates the preview URL on certain actions. Each interval it will execute the following logic:
- Check if we have
.editor-post-preview
and.editor-post-save-draft
and that the href is not the same asget_preview_post_link()
- If yes -> continue with the next steps || if no -> finish
- Update href with the correct
get_preview_post_link()
- Remove the listener events added by Gutenberg from the button
- Add our listener event that will save the draft and then open
get_preview_post_link()
in a new tab on clickUse at your own risk! It might break on future Gutenberg versions
Tested on Wordpress 5.3.2
Modified this function so each preview click doesn't open the new tab 👍
setTimeout(function() {
const win = window.open("' . get_preview_post_link() . '", editorPreviewButton.attr("target"));
if (typeof win.name === "undefined") win.name = editorPreviewButton.attr("target");
if (win) {
win.focus();
}
}, 1500);
I'm having the same problem and """solved""" it with a workaround (notice the triple double quotes). Maybe it could help some people until there's an official solution. Feel free to use and/or improve this workaround.
Add this in your theme's functions.php// workaround script until there's an official solution for https://github.com/WordPress/gutenberg/issues/13998 function fix_preview_link_on_draft() { echo '<script type="text/javascript"> jQuery(document).ready(function () { const checkPreviewInterval = setInterval(checkPreview, 1000); function checkPreview() { const editorPreviewButton = jQuery(".editor-post-preview"); const editorPostSaveDraft = jQuery(".editor-post-save-draft"); if (editorPostSaveDraft.length && editorPreviewButton.length && editorPreviewButton.attr("href") !== "' . get_preview_post_link() . '" ) { editorPreviewButton.attr("href", "' . get_preview_post_link() . '"); editorPreviewButton.off(); editorPreviewButton.click(false); editorPreviewButton.on("click", function() { editorPostSaveDraft.click(); setTimeout(function() { const win = window.open("' . get_preview_post_link() . '", "_blank"); if (win) { win.focus(); } }, 1000); }); } } }); </script>'; } add_action('admin_footer', 'fix_preview_link_on_draft');
The script is running every second as Gutenberg also updates the preview URL on certain actions. Each interval it will execute the following logic:
- Check if we have
.editor-post-preview
and.editor-post-save-draft
and that the href is not the same asget_preview_post_link()
- If yes -> continue with the next steps || if no -> finish
- Update href with the correct
get_preview_post_link()
- Remove the listener events added by Gutenberg from the button
- Add our listener event that will save the draft and then open
get_preview_post_link()
in a new tab on clickUse at your own risk! It might break on future Gutenberg versions
Tested on Wordpress 5.3.2Modified this function so each preview click doesn't open the new tab 👍
setTimeout(function() { const win = window.open("' . get_preview_post_link() . '", editorPreviewButton.attr("target")); if (typeof win.name === "undefined") win.name = editorPreviewButton.attr("target"); if (win) { win.focus(); } }, 1500);
Well, in order to not spread this all over the backend pages, I've used these hooks/actions instead:
add_action( 'admin_footer-edit.php', 'fix_preview_link_on_draft' ); // Fired on the page with the posts table
add_action( 'admin_footer-post.php', 'fix_preview_link_on_draft' ); // Fired on post edit page
add_action( 'admin_footer-post-new.php', 'fix_preview_link_on_draft' ); // Fired on add new post page
from here.
Most helpful comment
Is there any news on this at all? :)