Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Removed extraneous previews in favour of single preview pane. Simplified preview to single class in preparation to pipeline the markdown before rendering as HTML.

Authordjarvis <email>
Date2016-10-20 00:11:25 GMT-0700
Commitda1354de143ae2b562bc3f7ed04fcbd3cccfc866
Parent1b19e46
src/main/java/com/scrivendor/Constants.java
*/
public class Constants {
-
+
/**
* Prevent instantiation.
*/
private Constants() {
-
}
src/main/java/com/scrivendor/FileEditor.java
package com.scrivendor;
+import com.scrivendor.editor.MarkdownEditorPane;
+import com.scrivendor.preview.MarkdownPreviewPane;
+import com.scrivendor.service.Options;
import java.io.IOException;
import java.nio.file.Files;
import javafx.scene.text.Text;
import org.fxmisc.undo.UndoManager;
-import com.scrivendor.editor.MarkdownEditorPane;
-import com.scrivendor.preview.MarkdownPreviewPane;
-import com.scrivendor.service.Options;
/**
* Editor for a single file.
*
* @author Karl Tauber
*/
class FileEditor {
-
+
private final Options options = Services.load( Options.class );
canRedo.bind( undoManager.redoAvailableProperty() );
- SplitPane splitPane = new SplitPane( markdownEditorPane.getNode(), markdownPreviewPane.getNode() );
+ SplitPane splitPane = new SplitPane(
+ markdownEditorPane.getNode(),
+ markdownPreviewPane.getNode() );
tab.setContent( splitPane );
try {
- bytes = markdown.getBytes(getOptions().getEncoding() );
+ bytes = markdown.getBytes( getOptions().getEncoding() );
} catch( Exception ex ) {
bytes = markdown.getBytes();
}
}
-
+
private Options getOptions() {
return this.options;
src/main/java/com/scrivendor/FileEditorTabPane.java
package com.scrivendor;
+import com.scrivendor.service.Settings;
+import com.scrivendor.ui.AbstractPane;
+import com.scrivendor.util.Utils;
import java.io.File;
import java.nio.file.Path;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
-import com.scrivendor.service.Settings;
-import com.scrivendor.ui.AbstractPane;
-import com.scrivendor.util.Utils;
/**
src/main/java/com/scrivendor/Messages.java
/**
- * Recursively resolves message properties. Property values can refer
- * to other properties using a <code>${var}</code> syntax.
+ * Recursively resolves message properties. Property values can refer to other
+ * properties using a <code>${var}</code> syntax.
*
* @author Karl Tauber, Dave Jarvis
*/
private static String resolve( ResourceBundle props, String s ) {
+ final int len = s.length();
+ final Stack<StringBuilder> stack = new Stack<>();
+
StringBuilder sb = new StringBuilder( 256 );
- Stack<StringBuilder> stack = new Stack<>();
- int len = s.length();
+ boolean open = false;
for( int i = 0; i < len; i++ ) {
- char c = s.charAt( i );
+ final char c = s.charAt( i );
switch( c ) {
case '$': {
if( i + 1 < len && s.charAt( i + 1 ) == '{' ) {
stack.push( sb );
sb = new StringBuilder( 256 );
i++;
+ open = true;
}
+
break;
}
case '}': {
- if( stack.isEmpty() ) {
- throw new IllegalArgumentException( "unexpected '}'" );
- }
-
- String name = sb.toString();
+ if( open ) {
+ open = false;
+ final String name = sb.toString();
- sb = stack.pop();
- sb.append( props.getString( name ) );
- break;
+ sb = stack.pop();
+ sb.append( props.getString( name ) );
+ break;
+ }
}
default: {
sb.append( c );
break;
}
}
}
- if( !stack.isEmpty() ) {
+ if( open ) {
throw new IllegalArgumentException( "missing '}'" );
}
src/main/java/com/scrivendor/dialogs/ImageDialog.java
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-
package com.scrivendor.dialogs;
* @author Karl Tauber
*/
-public class ImageDialog
- extends Dialog<String>
-{
- private final StringProperty image = new SimpleStringProperty();
+public class ImageDialog extends Dialog<String> {
- public ImageDialog(Window owner, Path basePath) {
- setTitle(Messages.get("ImageDialog.title"));
- initOwner(owner);
- setResizable(true);
+ private final StringProperty image = new SimpleStringProperty();
- initComponents();
+ public ImageDialog( Window owner, Path basePath ) {
+ setTitle( Messages.get( "ImageDialog.title" ) );
+ initOwner( owner );
+ setResizable( true );
- linkBrowseFileButton.setBasePath(basePath);
- linkBrowseFileButton.addExtensionFilter(new ExtensionFilter(Messages.get("ImageDialog.chooser.imagesFilter"), "*.png", "*.gif", "*.jpg"));
- linkBrowseFileButton.urlProperty().bindBidirectional(urlField.escapedTextProperty());
+ initComponents();
- DialogPane dialogPane = getDialogPane();
- dialogPane.setContent(pane);
- dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
+ linkBrowseFileButton.setBasePath( basePath );
+ linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( Messages.get( "ImageDialog.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) );
+ linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() );
- dialogPane.lookupButton(ButtonType.OK).disableProperty().bind(
- urlField.escapedTextProperty().isEmpty()
- .or(textField.escapedTextProperty().isEmpty()));
+ DialogPane dialogPane = getDialogPane();
+ dialogPane.setContent( pane );
+ dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
- image.bind(Bindings.when(titleField.escapedTextProperty().isNotEmpty())
- .then(Bindings.format("![%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty()))
- .otherwise(Bindings.format("![%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty())));
- previewField.textProperty().bind(image);
+ dialogPane.lookupButton( ButtonType.OK ).disableProperty().bind(
+ urlField.escapedTextProperty().isEmpty()
+ .or( textField.escapedTextProperty().isEmpty() ) );
- setResultConverter(dialogButton -> {
- ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null;
- return (data == ButtonData.OK_DONE) ? image.get() : null;
- });
+ image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() )
+ .then( Bindings.format( "![%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) )
+ .otherwise( Bindings.format( "![%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) );
+ previewField.textProperty().bind( image );
- Platform.runLater(() -> {
- urlField.requestFocus();
+ setResultConverter( dialogButton -> {
+ ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null;
+ return (data == ButtonData.OK_DONE) ? image.get() : null;
+ } );
- if (urlField.getText().startsWith("http://"))
- urlField.selectRange("http://".length(), urlField.getLength());
- });
- }
+ Platform.runLater( () -> {
+ urlField.requestFocus();
- private void initComponents() {
- // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
- pane = new MigPane();
- Label urlLabel = new Label();
- urlField = new EscapeTextField();
- linkBrowseFileButton = new BrowseFileButton();
- Label textLabel = new Label();
- textField = new EscapeTextField();
- Label titleLabel = new Label();
- titleField = new EscapeTextField();
- Label previewLabel = new Label();
- previewField = new Label();
+ if( urlField.getText().startsWith( "http://" ) ) {
+ urlField.selectRange( "http://".length(), urlField.getLength() );
+ }
+ } );
+ }
- //======== pane ========
- {
- pane.setCols("[shrink 0,fill][300,grow,fill][fill]");
- pane.setRows("[][][][]");
+ private void initComponents() {
+ // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
+ pane = new MigPane();
+ Label urlLabel = new Label();
+ urlField = new EscapeTextField();
+ linkBrowseFileButton = new BrowseFileButton();
+ Label textLabel = new Label();
+ textField = new EscapeTextField();
+ Label titleLabel = new Label();
+ titleField = new EscapeTextField();
+ Label previewLabel = new Label();
+ previewField = new Label();
- //---- urlLabel ----
- urlLabel.setText(Messages.get("ImageDialog.urlLabel.text"));
- pane.add(urlLabel, "cell 0 0");
+ //======== pane ========
+ {
+ pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" );
+ pane.setRows( "[][][][]" );
- //---- urlField ----
- urlField.setEscapeCharacters("()");
- urlField.setText("http://yourlink.com");
- urlField.setPromptText("http://yourlink.com");
- pane.add(urlField, "cell 1 0");
- pane.add(linkBrowseFileButton, "cell 2 0");
+ //---- urlLabel ----
+ urlLabel.setText( Messages.get( "ImageDialog.urlLabel.text" ) );
+ pane.add( urlLabel, "cell 0 0" );
- //---- textLabel ----
- textLabel.setText(Messages.get("ImageDialog.textLabel.text"));
- pane.add(textLabel, "cell 0 1");
+ //---- urlField ----
+ urlField.setEscapeCharacters( "()" );
+ urlField.setText( "http://yourlink.com" );
+ urlField.setPromptText( "http://yourlink.com" );
+ pane.add( urlField, "cell 1 0" );
+ pane.add( linkBrowseFileButton, "cell 2 0" );
- //---- textField ----
- textField.setEscapeCharacters("[]");
- pane.add(textField, "cell 1 1 2 1");
+ //---- textLabel ----
+ textLabel.setText( Messages.get( "ImageDialog.textLabel.text" ) );
+ pane.add( textLabel, "cell 0 1" );
- //---- titleLabel ----
- titleLabel.setText(Messages.get("ImageDialog.titleLabel.text"));
- pane.add(titleLabel, "cell 0 2");
- pane.add(titleField, "cell 1 2 2 1");
+ //---- textField ----
+ textField.setEscapeCharacters( "[]" );
+ pane.add( textField, "cell 1 1 2 1" );
- //---- previewLabel ----
- previewLabel.setText(Messages.get("ImageDialog.previewLabel.text"));
- pane.add(previewLabel, "cell 0 3");
- pane.add(previewField, "cell 1 3 2 1");
- }
- // JFormDesigner - End of component initialization //GEN-END:initComponents
- }
+ //---- titleLabel ----
+ titleLabel.setText( Messages.get( "ImageDialog.titleLabel.text" ) );
+ pane.add( titleLabel, "cell 0 2" );
+ pane.add( titleField, "cell 1 2 2 1" );
- // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
- private MigPane pane;
- private EscapeTextField urlField;
- private BrowseFileButton linkBrowseFileButton;
- private EscapeTextField textField;
- private EscapeTextField titleField;
- private Label previewField;
+ //---- previewLabel ----
+ previewLabel.setText( Messages.get( "ImageDialog.previewLabel.text" ) );
+ pane.add( previewLabel, "cell 0 3" );
+ pane.add( previewField, "cell 1 3 2 1" );
+ }
+ // JFormDesigner - End of component initialization //GEN-END:initComponents
+ }
+
+ // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
+ private MigPane pane;
+ private EscapeTextField urlField;
+ private BrowseFileButton linkBrowseFileButton;
+ private EscapeTextField textField;
+ private EscapeTextField titleField;
+ private Label previewField;
// JFormDesigner - End of variables declaration //GEN-END:variables
}
src/main/java/com/scrivendor/editor/MarkdownEditorPane.java
import static javafx.scene.input.KeyCode.D;
import static javafx.scene.input.KeyCode.ENTER;
-import static javafx.scene.input.KeyCode.W;
-import static javafx.scene.input.KeyCombination.ALT_DOWN;
import static javafx.scene.input.KeyCombination.SHORTCUT_DOWN;
import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.VBox;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.StyleClassedTextArea;
* Markdown editor pane.
*
- * Uses pegdown (https://github.com/sirthias/pegdown) for parsing markdown.
+ * Uses pegdown (https://github.com/sirthias/pegdown) for styling the markdown
+ * content within a text area.
*
* @author Karl Tauber
Nodes.addInputMap( textArea, sequence(
consume( keyPressed( ENTER ), this::enterPressed ),
- consume( keyPressed( D, SHORTCUT_DOWN ), this::deleteLine ),
- consume( keyPressed( W, ALT_DOWN ), this::showWhitespace )
+ consume( keyPressed( D, SHORTCUT_DOWN ), this::deleteLine )
) );
textArea.estimatedScrollYProperty().addListener( scrollYListener );
textArea.totalHeightEstimateProperty().addListener( scrollYListener );
+
+ VBox container = new VBox();
+ container.getChildren().add( textArea );
// create scroll pane
- scrollPane = new VirtualizedScrollPane<>( textArea );
+ scrollPane = new VirtualizedScrollPane<>( textArea );
overlayGraphicFactory = new ParagraphOverlayGraphicFactory( textArea );
return; // editor closed but not yet GCed
}
+
if( e == getOptions().markdownExtensionsProperty() ) {
// re-process markdown if markdown extensions option changes
pegDownProcessor = null;
textChanged( textArea.getText() );
} else if( e == getOptions().showWhitespaceProperty() ) {
updateShowWhitespace();
}
};
+
WeakInvalidationListener weakOptionsListener = new WeakInvalidationListener( optionsListener );
getOptions().markdownExtensionsProperty().addListener( weakOptionsListener );
applyHighlighting( astRoot );
markdownAST.set( astRoot );
+ }
+
+ private synchronized PegDownProcessor getPegDownProcessor() {
+ if( this.pegDownProcessor == null ) {
+ this.pegDownProcessor = new PegDownProcessor( getOptions().getMarkdownExtensions() );
+ }
+
+ return this.pegDownProcessor;
}
private RootNode parseMarkdown( String text ) {
- if( pegDownProcessor == null ) {
- pegDownProcessor = new PegDownProcessor( getOptions().getMarkdownExtensions() );
- }
- return pegDownProcessor.parseMarkdown( text.toCharArray() );
+ return getPegDownProcessor().parseMarkdown( text.toCharArray() );
}
newText = newText.concat( matcher.group( 1 ) );
} else {
- // current line contains only whitespace characters and list markers
+ // current line contains only whitespace characters and list markers
// --> empty current line
int caretPosition = textArea.getCaretPosition();
public void surroundSelection( String leading, String trailing, String hint ) {
- // Note: not using textArea.insertText() to insert leading and trailing
+ // Note: not using textArea.insertText() to insert leading and trailing
// because this would add two changes to undo history
}
- // remove leading line separators from leading text
+ // remove leading line separators from leading text
// if there are line separators before the selected text
if( leading.startsWith( "\n" ) ) {
}
- // remove trailing line separators from trailing or leading text
+ // remove trailing line separators from trailing or leading text
// if there are line separators after the selected text
boolean trailingIsEmpty = trailing.isEmpty();
src/main/java/com/scrivendor/preview/ASTPreview.java
-/*
- * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * o Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * o Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.scrivendor.preview;
-
-import java.nio.file.Path;
-import javafx.scene.Node;
-import javafx.scene.control.ScrollBar;
-import javafx.scene.control.TextArea;
-import com.scrivendor.util.Utils;
-import org.parboiled.support.ToStringFormatter;
-import org.parboiled.trees.GraphUtils;
-import org.pegdown.ast.RootNode;
-
-/**
- * Pegdown AST preview.
- * Prints the AST tree to a text area.
- *
- * @author Karl Tauber
- */
-class ASTPreview
- implements MarkdownPreviewPane.Preview
-{
- private final TextArea textArea = new TextArea();
- private ScrollBar vScrollBar;
-
- ASTPreview() {
- textArea.setEditable(false);
- }
-
- Node getNode() {
- return textArea;
- }
-
- @Override
- public void update(RootNode astRoot, Path path) {
- double scrollTop = textArea.getScrollTop();
- double scrollLeft = textArea.getScrollLeft();
-
- textArea.setText(GraphUtils.printTree(astRoot, new ToStringFormatter<>()));
-
- textArea.setScrollTop(scrollTop);
- textArea.setScrollLeft(scrollLeft);
- }
-
- @Override
- public void scrollY(double value) {
- if (vScrollBar == null)
- vScrollBar = Utils.findVScrollBar(textArea);
- if (vScrollBar == null)
- return;
-
- double maxValue = vScrollBar.maxProperty().get();
- vScrollBar.setValue(maxValue * value);
- }
-}
src/main/java/com/scrivendor/preview/HtmlSourcePreview.java
-/*
- * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * o Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * o Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.scrivendor.preview;
-
-import java.nio.file.Path;
-import javafx.scene.Node;
-import javafx.scene.control.ScrollBar;
-import javafx.scene.control.TextArea;
-import com.scrivendor.util.Utils;
-import org.pegdown.ast.RootNode;
-
-/**
- * HTML source preview.
- *
- * @author Karl Tauber
- */
-class HtmlSourcePreview
- implements MarkdownPreviewPane.Preview
-{
- private final TextArea textArea = new TextArea();
- private ScrollBar vScrollBar;
-
- HtmlSourcePreview() {
- textArea.setEditable(false);
- textArea.setWrapText(true);
- }
-
- Node getNode() {
- return textArea;
- }
-
- @Override
- public void update(RootNode astRoot, Path path) {
- double scrollTop = textArea.getScrollTop();
-
- textArea.setText(WebViewPreview.toHtml(astRoot));
-
- textArea.setScrollTop(scrollTop);
- }
-
- @Override
- public void scrollY(double value) {
- if (vScrollBar == null)
- vScrollBar = Utils.findVScrollBar(textArea);
- if (vScrollBar == null)
- return;
-
- double maxValue = vScrollBar.maxProperty().get();
- vScrollBar.setValue(maxValue * value);
- }
-}
src/main/java/com/scrivendor/preview/MarkdownPreviewPane.java
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-
package com.scrivendor.preview;
import java.nio.file.Path;
+import java.util.Collections;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
-import javafx.geometry.Side;
import javafx.scene.Node;
-import javafx.scene.control.Tab;
-import javafx.scene.control.TabPane;
-import javafx.scene.control.TabPane.TabClosingPolicy;
-import com.scrivendor.Messages;
+import javafx.scene.control.ScrollPane;
+import static javafx.scene.control.ScrollPane.ScrollBarPolicy.ALWAYS;
+import javafx.scene.web.WebEngine;
+import javafx.scene.web.WebView;
+import org.pegdown.LinkRenderer;
+import org.pegdown.ToHtmlSerializer;
+import org.pegdown.VerbatimSerializer;
import org.pegdown.ast.RootNode;
+import org.pegdown.plugins.PegDownPlugins;
/**
* Markdown preview pane.
- *
- * Uses pegdown AST.
*
* @author Karl Tauber
*/
-public class MarkdownPreviewPane
-{
- private final TabPane tabPane = new TabPane();
- private final WebViewPreview webViewPreview = new WebViewPreview();
- private final HtmlSourcePreview htmlSourcePreview = new HtmlSourcePreview();
- private final ASTPreview astPreview = new ASTPreview();
+public class MarkdownPreviewPane extends ScrollPane {
- interface Preview {
- void update(RootNode astRoot, Path path);
- void scrollY(double value);
- }
+ private final ObjectProperty<RootNode> markdownAST = new SimpleObjectProperty<>();
+ private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
+ private final DoubleProperty scrollY = new SimpleDoubleProperty();
- public MarkdownPreviewPane() {
- tabPane.setSide(Side.BOTTOM);
- tabPane.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
+ private final WebView webView = new WebView();
+ private int lastScrollX;
+ private int lastScrollY;
- Tab webViewTab = new Tab(Messages.get("MarkdownPreviewPane.webViewTab"), webViewPreview.getNode());
- webViewTab.setUserData(webViewPreview);
- tabPane.getTabs().add(webViewTab);
+ private boolean delayScroll;
- Tab htmlSourceTab = new Tab(Messages.get("MarkdownPreviewPane.htmlSourceTab"), htmlSourcePreview.getNode());
- htmlSourceTab.setUserData(htmlSourcePreview);
- tabPane.getTabs().add(htmlSourceTab);
+ public MarkdownPreviewPane() {
+ setVbarPolicy( ALWAYS );
- Tab astTab = new Tab(Messages.get("MarkdownPreviewPane.astTab"), astPreview.getNode());
- astTab.setUserData(astPreview);
- tabPane.getTabs().add(astTab);
+ markdownASTProperty().addListener( (observable, oldValue, newValue) -> {
+ update();
+ } );
- tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldTab, newTab) -> {
- update();
- scrollY();
- });
+ pathProperty().addListener( (observable, oldValue, newValue) -> {
+ update();
+ } );
- path.addListener((observable, oldValue, newValue) -> {
- update();
- });
+ scrollYProperty().addListener( (observable, oldValue, newValue) -> {
+ scrollY();
+ } );
+ }
- markdownAST.addListener((observable, oldValue, newValue) -> {
- update();
- });
+ private String toHtml() {
+ final RootNode root = getMarkdownAST();
- scrollY.addListener((observable, oldValue, newValue) -> {
- scrollY();
- });
- }
+ return root == null
+ ? ""
+ : new ToHtmlSerializer( new LinkRenderer(),
+ Collections.<String, VerbatimSerializer>emptyMap(),
+ PegDownPlugins.NONE.getHtmlSerializerPlugins() ).toHtml( root );
+ }
- public Node getNode() {
- return tabPane;
- }
+ public void update() {
+ if( !getEngine().getLoadWorker().isRunning() ) {
+ setScrollXY();
+ }
- private Preview getActivePreview() {
- return (Preview) tabPane.getSelectionModel().getSelectedItem().getUserData();
- }
+ getEngine().loadContent(
+ "<!DOCTYPE html>"
+ + "<html>"
+ + "<head>"
+ + "<link rel='stylesheet' href='" + getClass().getResource( "markdownpad-github.css" ) + "'>"
+ + getBase()
+ + "</head>"
+ + "<body" + getScrollScript() + ">"
+ + toHtml()
+ + "</body>"
+ + "</html>" );
+ }
- private void update() {
- getActivePreview().update(getMarkdownAST(), getPath());
- }
+ /**
+ * Obtain the window.scrollX and window.scrollY from web engine, but only no
+ * worker is running (in this case the result would be zero).
+ */
+ private void setScrollXY() {
+ lastScrollX = getNumber( execute( "window.scrollX" ) );
+ lastScrollY = getNumber( execute( "window.scrollY" ) );
+ }
- private boolean scrollYrunLaterPending;
- private void scrollY() {
- // avoid too many (and useless) runLater() invocations
- if (scrollYrunLaterPending)
- return;
- scrollYrunLaterPending = true;
+ private int getNumber( final Object number ) {
+ return (number instanceof Number) ? ((Number)number).intValue() : 0;
+ }
- Platform.runLater(() -> {
- scrollYrunLaterPending = false;
- getActivePreview().scrollY(getScrollY());
- });
- }
+ private String getBase() {
+ final Path path = getPath();
- // 'path' property
- private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
- public Path getPath() { return path.get(); }
- public void setPath(Path path) { this.path.set(path); }
- public ObjectProperty<Path> pathProperty() { return path; }
+ return path == null
+ ? ""
+ : ("<base href='" + path.getParent().toUri().toString() + "'>");
+ }
- // 'markdownAST' property
- private final ObjectProperty<RootNode> markdownAST = new SimpleObjectProperty<RootNode>();
- public RootNode getMarkdownAST() { return markdownAST.get(); }
- public void setMarkdownAST(RootNode astRoot) { markdownAST.set(astRoot); }
- public ObjectProperty<RootNode> markdownASTProperty() { return markdownAST; }
+ private String getScrollScript() {
+ return (lastScrollX > 0 || lastScrollY > 0)
+ ? (" onload='window.scrollTo(" + lastScrollX + "," + lastScrollY + ");'")
+ : "";
+ }
- // 'scrollY' property
- private final DoubleProperty scrollY = new SimpleDoubleProperty();
- public double getScrollY() { return scrollY.get(); }
- public void setScrollY(double value) { scrollY.set(value); }
- public DoubleProperty scrollYProperty() { return scrollY; }
+ /**
+ * Helps avoid many superfluous runLater() calls.
+ */
+ private void scrollY() {
+ if( !delayScroll ) {
+ delayScroll = true;
+
+ Platform.runLater( () -> {
+ delayScroll = false;
+ scrollY( getScrollY() );
+ } );
+ }
+ }
+
+ private void scrollY( double value ) {
+ execute(
+ "window.scrollTo(0, (document.body.scrollHeight - window.innerHeight) * "
+ + value
+ + ");" );
+ }
+
+ public Path getPath() {
+ return pathProperty().get();
+ }
+
+ public void setPath( Path path ) {
+ pathProperty().set( path );
+ }
+
+ public ObjectProperty<Path> pathProperty() {
+ return this.path;
+ }
+
+ public RootNode getMarkdownAST() {
+ return markdownASTProperty().get();
+ }
+
+ public void setMarkdownAST( RootNode astRoot ) {
+ markdownASTProperty().set( astRoot );
+ }
+
+ public ObjectProperty<RootNode> markdownASTProperty() {
+ return this.markdownAST;
+ }
+
+ public double getScrollY() {
+ return scrollYProperty().get();
+ }
+
+ public void setScrollY( double value ) {
+ scrollYProperty().set( value );
+ }
+
+ public DoubleProperty scrollYProperty() {
+ return this.scrollY;
+ }
+
+ public Node getNode() {
+ return getWebView();
+ }
+
+ private Object execute( String script ) {
+ return getEngine().executeScript( script );
+ }
+
+ private WebEngine getEngine() {
+ return getWebView().getEngine();
+ }
+
+ private WebView getWebView() {
+ return this.webView;
+ }
}
src/main/java/com/scrivendor/preview/WebViewPreview.java
-/*
- * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * o Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * o Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.scrivendor.preview;
-
-import java.nio.file.Path;
-import java.util.Collections;
-import javafx.scene.Node;
-import javafx.scene.web.WebView;
-import org.pegdown.LinkRenderer;
-import org.pegdown.ToHtmlSerializer;
-import org.pegdown.VerbatimSerializer;
-import org.pegdown.ast.RootNode;
-import org.pegdown.plugins.PegDownPlugins;
-
-/**
- * WebView preview.
- * Serializes the AST tree to HTML and shows it in a WebView.
- *
- * @author Karl Tauber
- */
-class WebViewPreview
- implements MarkdownPreviewPane.Preview
-{
- private final WebView webView = new WebView();
- private int lastScrollX;
- private int lastScrollY;
-
- Node getNode() {
- return webView;
- }
-
- static String toHtml(RootNode astRoot) {
- if (astRoot == null)
- return "";
- return new ToHtmlSerializer(new LinkRenderer(),
- Collections.<String, VerbatimSerializer>emptyMap(),
- PegDownPlugins.NONE.getHtmlSerializerPlugins())
- .toHtml(astRoot);
- }
-
- @Override
- public void update(RootNode astRoot, Path path) {
- if (!webView.getEngine().getLoadWorker().isRunning()) {
- // get window.scrollX and window.scrollY from web engine,
- // but only no worker is running (in this case the result would be zero)
- Object scrollXobj = webView.getEngine().executeScript("window.scrollX");
- Object scrollYobj = webView.getEngine().executeScript("window.scrollY");
- lastScrollX = (scrollXobj instanceof Number) ? ((Number)scrollXobj).intValue() : 0;
- lastScrollY = (scrollYobj instanceof Number) ? ((Number)scrollYobj).intValue() : 0;
- }
-
- String base = (path != null)
- ? ("<base href=\"" + path.getParent().toUri().toString() + "\">\n")
- : "";
- String scrollScript = (lastScrollX > 0 || lastScrollY > 0)
- ? (" onload='window.scrollTo("+lastScrollX+", "+lastScrollY+");'")
- : "";
-
- webView.getEngine().loadContent(
- "<!DOCTYPE html>\n"
- + "<html>\n"
- + "<head>\n"
- + "<link rel=\"stylesheet\" href=\"" + getClass().getResource("markdownpad-github.css") + "\">\n"
- + base
- + "</head>\n"
- + "<body" + scrollScript + ">\n"
- + toHtml(astRoot)
- + "</body>\n"
- + "</html>");
- }
-
- @Override
- public void scrollY(double value) {
- webView.getEngine().executeScript(
- "window.scrollTo(0, (document.body.scrollHeight - window.innerHeight) * "+value+");");
- }
-}
src/main/java/com/scrivendor/service/Configuration.java
/*
- * Copyright (c) 2016 White Magic Software, Inc.
+ * Copyright 2016 White Magic Software, Inc.
*
* All rights reserved.
src/main/java/com/scrivendor/service/Pipeline.java
+/*
+ * Copyright 2016 White Magic Software, Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.scrivendor.service;
+
+/**
+ *
+ * @author White Magic Software, Ltd.
+ */
+public interface Pipeline extends Service {
+
+ public String process( String content );
+}
src/main/resources/com/scrivendor/editor/Markdown.css
.markdown-editor {
- -fx-font-size: 14px;
+ -fx-font-size: 14px;
}
-
/*---- headers ----*/
-.markdown-editor .h1 { -fx-font-size: 2em; }
+.markdown-editor .h1 { -fx-font-size: 2.25em; }
.markdown-editor .h2 { -fx-font-size: 1.75em; }
.markdown-editor .h3 { -fx-font-size: 1.5em; }
.markdown-editor .h5,
.markdown-editor .h6 {
- -fx-font-weight: bold;
- -fx-fill: derive(crimson, -20%);
+ -fx-font-weight: bold;
+ -fx-fill: derive(crimson, -20%);
}
/*---- inlines ----*/
.markdown-editor .strong {
- -fx-font-weight: bold;
+ -fx-font-weight: bold;
}
.markdown-editor .em {
- -fx-font-style: italic;
+ -fx-font-style: italic;
}
.markdown-editor .del {
- -fx-strikethrough: true;
+ -fx-strikethrough: true;
}
.markdown-editor .a {
- -fx-fill: #4183C4 !important;
+ -fx-fill: #4183C4 !important;
}
.markdown-editor .img {
- -fx-fill: #4183C4 !important;
+ -fx-fill: #4183C4 !important;
}
.markdown-editor .code {
- -fx-font-family: monospace;
- -fx-fill: #090 !important;
+ -fx-font-family: monospace;
+ -fx-fill: #090 !important;
}
/*---- blocks ----*/
.markdown-editor .pre {
- -fx-font-family: monospace;
- -fx-fill: #060 !important;
+ -fx-font-family: monospace;
+ -fx-fill: #060 !important;
}
.markdown-editor .blockquote {
- -fx-fill: #777;
+ -fx-fill: #777;
}
.markdown-editor .li {
- -fx-fill: #444;
+ -fx-fill: #444;
}
.markdown-editor .dl {
}
.markdown-editor .dt {
- -fx-font-weight: bold;
- -fx-font-style: italic;
+ -fx-font-weight: bold;
+ -fx-font-style: italic;
}
.markdown-editor .dd {
- -fx-fill: #444;
+ -fx-fill: #444;
}
/*---- table ----*/
.markdown-editor .table {
- -fx-font-family: monospace;
+ -fx-font-family: monospace;
}
.markdown-editor .th {
- -fx-font-weight: bold;
+ -fx-font-weight: bold;
}
.markdown-editor .html {
- -fx-font-family: monospace;
- -fx-fill: derive(crimson, -50%);
+ -fx-font-family: monospace;
+ -fx-fill: derive(crimson, -50%);
}
.markdown-editor .monospace {
- -fx-font-family: monospace;
+ -fx-font-family: monospace;
}
Delta342 lines added, 484 lines removed, 142-line decrease