Quick Tip - Previewer in separate window

Hi All,

Thought i would share this quick tip on a tiddler I created (see text below) … when opened as a separate window - it gives you a preview of any draft tiddler that is actively being edited…

<%if [<tv-history-list>get[current-tiddler]has[draft.of]get[draft.of]] %>

!!! Preview of <<condition>>
---
<$list filter="[<tv-history-list>get[current-tiddler]]" template="$:/core/ui/EditTemplate/body/preview/output" />
<%endif%>

Cheers
:slight_smile:

5 Likes

Very nice idea. Thanks for sharing.

1 Like

that’s fantastic!

What would it take to add a button to the editor toolbar that would open this as a new window at a single click?

Thanks @nemo - I was thinking of adding to the page controls - but this is a better idea… Its fairly easy to clone one of the existing edit toolbar buttons and use that… but here is my solution below in a tiddler $:/core/ui/Buttons/preview … Just need to tag this tiddler with the $:/tags/EditToolbar system tag to make sure it turns up.

\whitespace trim
<$button
	tooltip={{$:/language/Buttons/Preview/Hint}}
	aria-label={{$:/language/Buttons/Preview/Hint}}
	class=<<tv-config-toolbar-class>>
>
	<%if [<tv-config-toolbar-icons>match[yes]] %>
		{{$:/core/images/preview-open}}
	<%endif%>
	<%if [<tv-config-toolbar-text>match[yes]] %>
		<span class="tc-btn-text"><$text text={{$:/language/Buttons/Preview/Caption}}/></span>
	<%endif%>

<$action-sendmessage 
  $message="tm-open-window" 
  $param="Previewer" 
  windowTitle="Preview"
  width="640"
  height="480"/>
</$button>

Note that $param="Previewer" … “Previewer” is the name I gave my tiddler from the original post … if you name it differently then this line needs to be changed accordingly

1 Like

neat!

I was thinking $:/tags/EditorToolbar but that’s a small tweak (switch to that tag, and add field condition with value []. The resulting button in the editor toolbar is a little oversize on my system, but that could be my own custom CSS wranglings mussing things up - I’ll look into polishing that a little later (about to step out for most of the weekend)

1 Like

Hi @Christian_Byron and @nemo, I found this idea pretty neat and integrated it into the existing preview button (i.e. I cloned the button and hid the original button via the ControlPanel, to not overwrite any system tiddlers). This way, the button can be used to open and close the external window and changes icons depending on the current state. Also, depending on the chosen preview type, we can alternatively show e.g. the parse tree or the diff output in the external window. I find the parse and widget tree outputs especially benefit from the extra space.

Correspondingly, the thing consists of several tiddlers now:

The toolbar button:

button-classes: tc-text-editor-toolbar-item-start-group
caption: {{$:/language/Buttons/Preview/Caption}}
condition: [<targetTiddler>]
custom-icon: yes
description: {{$:/language/Buttons/Preview/Hint}}
icon: $:/core/images/preview-open
shortcuts: ((preview))
tags: $:/tags/EditorToolbar
title: $:/yaisog/ui/EditorToolbar/preview
type: text/vnd.tiddlywiki

\whitespace trim
<span>
	<% if [{$:/state/editpreviewexternal}match[yes]] %>
		<% if [window[EditorPreview]] %>
			{{$:/core/images/preview-open}}
		<% else %>
			{{$:/core/images/preview-closed}}
		<% endif %>
	<% else %>
		<$transclude $tiddler={{{ [<edit-preview-state>match[yes]then[$:/core/images/preview-open]else[$:/core/images/preview-closed]] }}} />
	<% endif %>
</span>

<% if [{$:/state/editpreviewexternal}match[yes]] %>
	<% if [window[EditorPreview]] %>
		<$action-sendmessage $message="tm-close-window" $param="EditorPreview" />
		<$action-deletetiddler $tiddler="$:/state/showeditpreview--external" /> <!-- not really necessary -->
	<% else %>
		<$action-sendmessage $message="tm-open-window"
							 $param="$:/yaisog/ui/EditorToolbar/preview-window"
							 template="$:/core/ui/ViewTemplate/body/default"
							 tv-history-list=<<tv-history-list>>
							 editPreviewStateTiddler="$:/state/showeditpreview--external"
							 windowID="EditorPreview"
							 windowTitle="Editor Preview"
							 width="1920" height="1080" />
		<$action-setfield $tiddler="$:/state/showeditpreview--external" text="yes" />
	<% endif %>
<% else %>
	<$action-setfield $tiddler=<<editPreviewStateTiddler>> $value={{{ [<editPreviewStateTiddler>get[text]toggle[yes],[no]] }}} />
<% endif %>
<$action-sendmessage $message="tm-edit-text-operation" $param="focus-editor"/> <!-- does not focus original window when opening external window -->

I only pass the editPreviewStateTiddler variable here because I use it in the ViewTemplateBodyFilters cascade to show a “plain” template for tiddler previews in edit mode (my default template is somewhat complex).

Then, there is the “button” for the type dropdown:

button-classes: tc-text-editor-toolbar-item-adjunct
caption: {{$:/language/Buttons/PreviewType/Caption}}
condition: [all[shadows+tiddlers]tag[$:/tags/EditPreview]!has[draft.of]butfirst[]limit[1]]
description: {{$:/language/Buttons/PreviewType/Hint}}
dropdown: $:/yaisog/ui/EditorToolbar/preview-type-dropdown
icon: $:/mwi/images-lucide/chevron-down
tags: $:/tags/EditorToolbar
title: $:/yaisog/ui/EditorToolbar/preview-type
type: text/vnd.tiddlywiki

This only points to this modified type dropdown:

title: $:/yaisog/ui/EditorToolbar/preview-type-dropdown
type: text/vnd.tiddlywiki

\procedure preview-type-button()
	<$button tag="a">
		<$action-setfield $tiddler="$:/state/editpreviewtype" $value=<<previewType>>/>
		<$action-deletetiddler $tiddler=<<dropdown-state>> />
		<$transclude tiddler=<<previewType>> field="caption" mode="inline">
			<$view tiddler=<<previewType>> field="title" mode="inline"/>
		</$transclude>
		<$reveal tag="span" state="$:/state/editpreviewtype" type="match" text=<<previewType>> default="$:/core/ui/EditTemplate/body/preview/output">
			<$entity entity="&nbsp;"/>
			<$entity entity="&#x2713;"/>
		</$reveal>
	</$button>
\end

<style>
	.preview-type-dropdown-checkbox {
		padding-inline: 1rem;
	}
</style>

<$list filter="[all[shadows+tiddlers]tag[$:/tags/EditPreview]!has[draft.of]]" variable="previewType">
	<<preview-type-button>>
</$list>

<span class="preview-type-dropdown-checkbox">
	<$checkbox tiddler="$:/state/editpreviewexternal" field="text" checked="yes" unchecked="no" default="no"
			   checkactions="""<$action-setfield $tiddler=<<editPreviewStateTiddler>> $value="no" />"""
			   uncheckactions="""<$action-sendmessage $message="tm-close-window" $param="EditorPreview" />""" >
		Open in external window
	</$checkbox>
</span>

This dropdown adds the checkbox to choose between the regular preview and the external window preview:
image
Deselecting the checkbox will make previews work as usual.

The preview window contents:

tags: 
title: $:/yaisog/ui/EditorToolbar/preview-window
type: text/vnd.tiddlywiki

\import [subfilter{$:/core/config/GlobalImportFilter}]

<% if [<tv-history-list>get[current-tiddler]get[draft.of]] %>
	<$tiddler tiddler={{{ [<tv-history-list>get[current-tiddler]] }}} >
		<$let tv-tiddler-preview="yes" >
			<$transclude $tiddler={{$:/state/editpreviewtype}} mode="inline">
				<$transclude $tiddler="$:/core/ui/EditTemplate/body/preview/output" />
			</$transclude>
		</$let>
	</$tiddler>
<% else %>
	Current tiddler is not in edit mode.
<% endif %>

The preview window can be closed by clicking the button again or by deselecting the checkbox.

To keep the button icon and actions correct even when closing the preview window by clicking its own close button, I had to write a custom filter function that checks for the existence of the preview window in TW’s window list:

/*\
title: $:/yaisog/modules/filters/window.js
type: application/javascript
module-type: filteroperator
\*/

"use strict";

/*
Export our filter function
*/
exports["window"] = function(source,operator,options) {
    var results = [],
		windowObj = null,
		windowID = operator.operand;

	if (typeof window !== 'undefined' && window.$tw && window.$tw.windows) {

        // If no parameter is provided, return all window names
        if (windowID === "") {
            for (var key in window.$tw.windows) {
                if (window.$tw.windows.hasOwnProperty(key)) {
                    var windowObj = window.$tw.windows[key];
                    if (windowObj) {
                        results.push(windowObj.name || "");
                    }
                }
            }
        } else {
            // Get specific window object by key
            var windowObj = null;
            if (window.$tw.windows.hasOwnProperty(windowID)) {
                windowObj = window.$tw.windows[windowID];
            }
            if (windowObj) {
                results.push(windowObj.name || "");
            }
        }
    }
    return results;
};

Don’t forget to reload the wiki after adding this JS tiddler.

2 Likes

Interesting stuff. It is kind of the reverse of what the SideEditor plugin does.

hmm, that plugin, at least on the demo for me, appears to have a bug that limits it’s usefulness - the edit window appears only while I hover over the main tid, but if I move the mouse off that tid towards the edit display, the edit disappears - so cannot be reached (without making the browser window smaller so they overlap anyway. Also solvable by switching to Fluid/Fixed)

I was already thinking it’s the reverse of the ‘QuickEdit’ I have in a tab in the sidebar, which gives live editing of a tid which will be reflected live in the main stream (if it’s loaded there, though there is no requirement that it be), or can popout to a separate edit window. It’s a mild variant from what was written by @diligent_path here: Project 2036: the future of TiddlyWiki - #72 by diligent_path

Between that and this previewer, I can either edit in the sidebar/popout window with live-preview in the TW stream, or I can edit in the TW stream with a live-preview in a popout window (noting I’ll update to @Yaisog’s version tomorrow - the idea looks great.

I expect to use both at different times too, depending on need and taste. The sidebar quickedit is almost always set to my wordle result tid, for easy adding of daily resultsd on the phone, but sometimes I load my custom CSS in there and then popout window, because testing CSS results via live-edit is SO much faster than a edit-save-check-edit-scroll_to_relevant_part_of_the_css. OTOH, most desktop browser editing I do in the normal/default ‘Draft of…’ style (benefit of edit toolbar buttons!) and I’m always torn between having edit room, vs getting a preview. So a popout window with the preview is a great option there!

IMHO the best software provides choice (ideally without being confusing to users - that’s the dilemma with choice), and having these two different directions of edit/preview as options, is something I genuinely expect to continue to make regular use of :smiley:

I don’t want to hijack this thread so just a quick check that you’re not misunderstanding anything; did you hover out at the scrollbar to re-show the editor? (The philosophy behind the plugin is to stay in the viewport and still allow full views of both the tiddler, its editor and the sidebar - and the means to easily flip between the three is by hovering at strategic places.)

oh huh, I get the edit view back by moving past where it had previously been visible, to the scrollbar at the edge of the browser window, and it came back. That’s… uh, non-intuitive?

I have a memory of a similar plugin which (IIRC) allowed multiple popups like that, but attached directly at the edge of the tid rather than on the other side of the sidebar. …I think I was thinking of this, and misremembering some details. Only one popup at a time: Floats + Nlite —

Might be interesting to spin this off to a new thread to collect up all the non-default variations of “edit+preview” that have been built by folks! (assuming there are more than the three four referenced in this thread so far)

Hey @Yaisog - Cool work - that’s a lot more functionality than i had imagined for this ‘quick tip’ - lol.

I was thinking about the closing of the preview window - in that I was wondering if its possible to track the window position at closure so that it could be re-opened in that same location. I think the core has some code around cleaning up the window id - so may give some clue as to how it could be done

:slight_smile:

Hi @Christian_Byron, in windows.js there is this section

srcWindow.addEventListener("beforeunload",function(event) {
		delete $tw.windows[windowID];
		$tw.wiki.removeEventListener("change",refreshHandler);
		},false);

where I think this would go. But I’d rather not mess with this.

What I could do is to change my window[] filter operator to return the coordinates of the window if it is open and then save these when closing the window using the Preview button. You’d need to use this button only once (as long as the $:/state tiddler persists) or only when you want a different position to be saved. I think that is preferrable to overwriting core tiddlers.

Let me look into this when I find the time.

Could we add an event listener ourselves to harvest the location data and window state into a $:\config\... tiddler ? Then you filter would be to see if that config tiddler has a given value for the window state (possibly done without a custom operation) ?

I had a quick look at this. It seems that browsers don’t update the screenX and screenY properties when windows are moved or resized, only width and height get updated. Also, I can’t set negative left coordinates on the preview window when opening it, to make it appear on a second monitor to the left – it will get clamped to 0.

I think there is little to be gained by trying to make TW remember the window size/position, other than hardcoding its size in the code for the preview button.
I’ll just open the window once per session, move it to the other monitor and leave it there.

If there is a way to make this work, let me know and I’ll take another look.

I managed to get working a variant of this idea that is compatible with @saqimtiaz 's Streams — on TiddlyWiki 5.2.2 plugin.

It is somewhat complicated, because the Streams node editing mechanism does not rely on the draft.of mechanism, and because you can concurrently edit multiple Stream nodes (in various Stream root tiddlers), in addition to editing the main body of yet another tiddler. This version will therefore preview all of the tiddlers being edited, whether they are Stream nodes or original text fields. Rendering the previews also requires a modified solution, as far as I was able to figure out, to show Stream nodes when they exist, but also to render Stream nodes for which the $:/core/ui/EditTemplate/body/preview/output template is not available.

Here’s my version:

<%if [<tv-history-list>get[current-tiddler]has[draft.of]get[draft.of]] %>

	!!! Preview of <<condition>>
	---
	<$list filter="[<tv-history-list>get[current-tiddler]]" template="$:/core/ui/EditTemplate/body/preview/output" />
	<$list filter="[<mycondition>has[stream-list]]" template="$:/plugins/sq/streams/stream-view-template"/>
<%endif%>

<%if [prefix[$:/state/sq/streams/current-edit-tiddler]] %>

	<$list filter="[prefix[$:/state/sq/streams/current-edit-tiddler]get[text]get-stream-root[]]" variable="mycondition">
	
		!!! Preview of <<mycondition>>
		---

		<$list filter="[<mycondition>]" template="$:/core/ui/ViewTemplate/body" />
		<$list filter="[<mycondition>]" template="$:/plugins/sq/streams/stream-view-template"/>
	</$list>
<%endif%>


<%if [<tv-history-list>get[current-tiddler]has[draft.of]get[draft.of]] [prefix[$:/state/sq/streams/current-edit-tiddler]] +[count[]compare:number:eq[0]] %>

	!!! No tiddler being edited
<%endif%>

image

Note: you need to also tag the preview tiddler with $:/tags/preview and modify the $:/plugins/sq/streams/templates/stream-row-template tiddler as follows, to trick it into displaying the rendered node being edited, instead of the editor.

change this line:
<$list filter="[<row-edit-state>get[text]!is[blank]else[]match<currentTiddler>]" variable="NULL" emptyMessage="""{{||$:/plugins/sq/streams/templates/stream-row-body}}""">

into this:
<$list filter="[<row-edit-state>get[text]!is[blank]else[]match<currentTiddler>] [<currentTiddler>tag[$:/tags/preview]]" variable="NULL" emptyMessage="""{{||$:/plugins/sq/streams/templates/stream-row-body}}""">

There are surely better ways…

There is also a plugin for this, I’ll link to my copy in my wiki as I’m on mobile without great signal to hunt down the orignal

preview window