Skip to content

Commit

Permalink
Add ability to create custom preview plugins
Browse files Browse the repository at this point in the history
- See Docs/Wiki/Plugins.md for more details
- Also created a new preference tab for the preview
  • Loading branch information
lukakerr committed Mar 24, 2019
1 parent 1bf69d4 commit 014bd8c
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 24 deletions.
File renamed without changes.
File renamed without changes.
32 changes: 32 additions & 0 deletions Docs/WIki/Plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Plugins

Creating a preview plugin is easy. Follow the steps below to get started:

1. Open the Pine preferences window and select the 'Preview' tab
2. Click 'Reveal Plugins'

At this point a directory named `Plugins` will appear. This is where all the active plugins are stored.

Plugins are written in JavaScript and loaded directly into the preview window.
This means you can do almost an manipulation you want to the elements displayed in the preview.

### An Example

Lets write a quick plugin to make all header elements appear green in color.

1. Create a file named `headers.js` and save it in the `Plugins` directory mentioned above

2. Inside this file, copy and paste the following JavaScript:

```javascript
var headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6");

for (var i = 0; i < headers.length; i++) {
headers[i].style.color = "green";
}
```

What this does it find every `<h..>` element and set its color to green.

3. Close and re-open Pine, and type `# A Green Heading!` into the editor, you'll now notice
the heading is shown as green in the preview.
46 changes: 36 additions & 10 deletions Pine.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,17 @@
A1BC8B13224214AF00623917 /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BC8B07224214AF00623917 /* NSView.swift */; };
A1BC8B152242162C00623917 /* CTLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BC8B142242162C00623917 /* CTLine.swift */; };
A1BC8B172242164000623917 /* CGPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BC8B162242164000623917 /* CGPoint.swift */; };
A1C26721221F9CE0006B5116 /* RELEASE.md in Resources */ = {isa = PBXBuildFile; fileRef = A1C2671F221F9CDF006B5116 /* RELEASE.md */; };
A1C26722221F9CE0006B5116 /* MARKDOWN_REFERENCE.md in Resources */ = {isa = PBXBuildFile; fileRef = A1C26720221F9CDF006B5116 /* MARKDOWN_REFERENCE.md */; };
A1C4020C221F851B00E3DBCC /* Reference.html in Resources */ = {isa = PBXBuildFile; fileRef = A1C4020B221F851B00E3DBCC /* Reference.html */; };
A1C40210221F868200E3DBCC /* ReferenceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C4020F221F868200E3DBCC /* ReferenceViewController.swift */; };
A1C65D82221179230053C422 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C65D81221179230053C422 /* DocumentController.swift */; };
A1C65FB620DE2B0D00326C60 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C65FB520DE2B0D00326C60 /* Alert.swift */; };
A1CFDA6E223D04D800ECFFA7 /* Autocomplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1CFDA6D223D04D800ECFFA7 /* Autocomplete.swift */; };
A1D4A0382234C77A00E2159A /* SidebarTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D4A0372234C77A00E2159A /* SidebarTableRowView.swift */; };
A1D5466420D4D9CC00D1221A /* Exporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D5466320D4D9CC00D1221A /* Exporter.swift */; };
A1D7173D2247573C003BE7BC /* PreviewStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D7173C2247573C003BE7BC /* PreviewStackView.swift */; };
A1D7173F2247584F003BE7BC /* SupportDirectory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D7173E2247584F003BE7BC /* SupportDirectory.swift */; };
A1D7174522475B21003BE7BC /* RELEASE.md in Resources */ = {isa = PBXBuildFile; fileRef = A1D7174222475B21003BE7BC /* RELEASE.md */; };
A1D7174622475B21003BE7BC /* MARKDOWN_REFERENCE.md in Resources */ = {isa = PBXBuildFile; fileRef = A1D7174322475B21003BE7BC /* MARKDOWN_REFERENCE.md */; };
A1E647DC20D1E9C000F32E55 /* PineSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E647DB20D1E9C000F32E55 /* PineSplitView.swift */; };
A1F122DF20DE0D27007DFD1A /* FileSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F122DE20DE0D27007DFD1A /* FileSaver.swift */; };
A1FB7A9A2227DC5900DC2E0E /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1FB7A992227DC5900DC2E0E /* UIStackView.swift */; };
Expand Down Expand Up @@ -167,15 +169,18 @@
A1BC8B07224214AF00623917 /* NSView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSView.swift; sourceTree = "<group>"; };
A1BC8B142242162C00623917 /* CTLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTLine.swift; sourceTree = "<group>"; };
A1BC8B162242164000623917 /* CGPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint.swift; sourceTree = "<group>"; };
A1C2671F221F9CDF006B5116 /* RELEASE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = RELEASE.md; sourceTree = "<group>"; };
A1C26720221F9CDF006B5116 /* MARKDOWN_REFERENCE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = MARKDOWN_REFERENCE.md; sourceTree = "<group>"; };
A1C4020B221F851B00E3DBCC /* Reference.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Reference.html; sourceTree = "<group>"; };
A1C4020F221F868200E3DBCC /* ReferenceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferenceViewController.swift; sourceTree = "<group>"; };
A1C65D81221179230053C422 /* DocumentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; };
A1C65FB520DE2B0D00326C60 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
A1CFDA6D223D04D800ECFFA7 /* Autocomplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Autocomplete.swift; sourceTree = "<group>"; };
A1D4A0372234C77A00E2159A /* SidebarTableRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarTableRowView.swift; sourceTree = "<group>"; };
A1D5466320D4D9CC00D1221A /* Exporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exporter.swift; sourceTree = "<group>"; };
A1D7173C2247573C003BE7BC /* PreviewStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStackView.swift; sourceTree = "<group>"; };
A1D7173E2247584F003BE7BC /* SupportDirectory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportDirectory.swift; sourceTree = "<group>"; };
A1D7174222475B21003BE7BC /* RELEASE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = RELEASE.md; sourceTree = "<group>"; };
A1D7174322475B21003BE7BC /* MARKDOWN_REFERENCE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = MARKDOWN_REFERENCE.md; sourceTree = "<group>"; };
A1D7174722475B2A003BE7BC /* Plugins.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Plugins.md; sourceTree = "<group>"; };
A1E647DB20D1E9C000F32E55 /* PineSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PineSplitView.swift; sourceTree = "<group>"; };
A1F122DE20DE0D27007DFD1A /* FileSaver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSaver.swift; sourceTree = "<group>"; };
A1FB7A992227DC5900DC2E0E /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -233,6 +238,7 @@
A1BC8AF6224203B100623917 /* Invisible.swift */,
A1904EA32228A06C0004FB82 /* PreferenceCategory.swift */,
A111013C20E85D9A001EBEDE /* SaveError.swift */,
A1D7173E2247584F003BE7BC /* SupportDirectory.swift */,
);
path = Enums;
sourceTree = "<group>";
Expand Down Expand Up @@ -306,7 +312,7 @@
A156AF742090606C0098BBDD = {
isa = PBXGroup;
children = (
A1C2671E221F9CDF006B5116 /* Docs */,
A1D7174022475B21003BE7BC /* Docs */,
AF85BB3E71369DF39A9597F6 /* Frameworks */,
E4FB2329AC3DC77FB4340593 /* Pods */,
A156AF7E2090606C0098BBDD /* Products */,
Expand Down Expand Up @@ -433,15 +439,32 @@
path = Managers;
sourceTree = "<group>";
};
A1C2671E221F9CDF006B5116 /* Docs */ = {
A1D7174022475B21003BE7BC /* Docs */ = {
isa = PBXGroup;
children = (
A1C2671F221F9CDF006B5116 /* RELEASE.md */,
A1C26720221F9CDF006B5116 /* MARKDOWN_REFERENCE.md */,
A1D7174122475B21003BE7BC /* Development */,
A1D7174422475B21003BE7BC /* WIki */,
);
path = Docs;
sourceTree = "<group>";
};
A1D7174122475B21003BE7BC /* Development */ = {
isa = PBXGroup;
children = (
A1D7174222475B21003BE7BC /* RELEASE.md */,
A1D7174322475B21003BE7BC /* MARKDOWN_REFERENCE.md */,
);
path = Development;
sourceTree = "<group>";
};
A1D7174422475B21003BE7BC /* WIki */ = {
isa = PBXGroup;
children = (
A1D7174722475B2A003BE7BC /* Plugins.md */,
);
path = WIki;
sourceTree = "<group>";
};
A1FB7AA32227F4E200DC2E0E /* Preferences */ = {
isa = PBXGroup;
children = (
Expand All @@ -451,6 +474,7 @@
A1FB7A9F2227EE9300DC2E0E /* PreferencesRoundedButton.swift */,
A1FB7A9D2227DFCB00DC2E0E /* PreferencesStackView.swift */,
A1FB7AA12227EEEF00DC2E0E /* PreferencesSwitchButton.swift */,
A1D7173C2247573C003BE7BC /* PreviewStackView.swift */,
A1FB7A992227DC5900DC2E0E /* UIStackView.swift */,
);
path = Preferences;
Expand Down Expand Up @@ -593,14 +617,14 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A1C26721221F9CE0006B5116 /* RELEASE.md in Resources */,
A1C4020C221F851B00E3DBCC /* Reference.html in Resources */,
A1C26722221F9CE0006B5116 /* MARKDOWN_REFERENCE.md in Resources */,
A165D1F6223DB66500A00821 /* autocomplete in Resources */,
A189EA742091E4F300DB0559 /* Markdown.css in Resources */,
A14BF5942241C4E600ED250D /* Credits.rtf in Resources */,
A124783C2233356400CA7A67 /* File.icns in Resources */,
A1D7174622475B21003BE7BC /* MARKDOWN_REFERENCE.md in Resources */,
A156AF872090606C0098BBDD /* Assets.xcassets in Resources */,
A1D7174522475B21003BE7BC /* RELEASE.md in Resources */,
A1FD269A20932CDD004D1C95 /* highlight-js in Resources */,
A156AF8A2090606D0098BBDD /* Main.storyboard in Resources */,
);
Expand Down Expand Up @@ -675,7 +699,9 @@
A156AF832090606C0098BBDD /* MarkdownViewController.swift in Sources */,
A1BC8AF52241FE0600623917 /* MarkdownLayoutManager.swift in Sources */,
A148C3A020914F0F00B1165C /* SyntaxThemes.swift in Sources */,
A1D7173F2247584F003BE7BC /* SupportDirectory.swift in Sources */,
A1BC8B10224214AF00623917 /* NSButton.swift in Sources */,
A1D7173D2247573C003BE7BC /* PreviewStackView.swift in Sources */,
A1C40210221F868200E3DBCC /* ReferenceViewController.swift in Sources */,
A156AFAF209061D60098BBDD /* PineWindowController.swift in Sources */,
A13C23FC20919672008557D0 /* PreviewViewController.swift in Sources */,
Expand Down
21 changes: 21 additions & 0 deletions Pine/Assets.xcassets/Preview.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "mac",
"filename" : "Preview.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"filename" : "[email protected]",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Binary file added Pine/Assets.xcassets/Preview.imageset/Preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions Pine/Controllers/Preferences/PreferencesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class PreferencesViewController: NSViewController {

private lazy var uiStackView = UIStackView()
private lazy var editorStackView = EditorStackView()
private lazy var previewStackView = PreviewStackView()
private lazy var markdownStackView = MarkdownStackView()
private lazy var documentStackView = DocumentStackView()

Expand Down Expand Up @@ -71,6 +72,8 @@ class PreferencesViewController: NSViewController {
views = uiStackView.getViews()
case .editor:
views = editorStackView.getViews()
case .preview:
views = previewStackView.getViews()
case .markdown:
views = markdownStackView.getViews()
case .document:
Expand Down
2 changes: 2 additions & 0 deletions Pine/Enums/PreferenceCategory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import Foundation
enum PreferenceCategory: String, CaseIterable {
case ui = "UI"
case editor = "Editor"
case preview = "Preview"
case markdown = "Markdown"
case document = "Document"

static let categories = [
ui,
editor,
preview,
markdown,
document
]
Expand Down
13 changes: 13 additions & 0 deletions Pine/Enums/SupportDirectory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// SupportDirectory.swift
// Pine
//
// Created by Luka Kerr on 24/3/19.
// Copyright © 2019 Luka Kerr. All rights reserved.
//

import Foundation

enum SupportDirectory: String {
case plugins = "Plugins"
}
30 changes: 30 additions & 0 deletions Pine/Helpers/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,34 @@ class Utils {
}
}

public static func getApplicationSupportDirectory(for directory: SupportDirectory? = nil) -> URL? {
let applicationSupportURL = try? FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)

guard
let applicationName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String,
let applicationFolder = applicationSupportURL?.appendingPathComponent(applicationName, isDirectory: true)
else { return nil }

if !FileManager.default.fileExists(atPath: applicationFolder.path) {
try? FileManager.default.createDirectory(at: applicationFolder, withIntermediateDirectories: true, attributes: nil)
}

if let dir = directory {
let directoryPath = applicationFolder.appendingPathComponent(dir.rawValue)

if !FileManager.default.fileExists(atPath: directoryPath.path) {
try? FileManager.default.createDirectory(at: directoryPath, withIntermediateDirectories: true, attributes: nil)
}

return directoryPath
}

return applicationFolder
}

}
20 changes: 20 additions & 0 deletions Pine/Models/HTML.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ class HTML {
private init() {
self.loadCSS()
self.loadJS()
self.loadPluginScripts()
}

var baseCSS: String = ""
var css: String = ""
var js: String = ""
var y: Int = 0

var pluginScripts: [String] = []

// The innerHTML contents are passed in here rather than stored
// to prevent asynchronous race conditions changing the content on startup
public func getHTML(with contents: String, direction: NSWritingDirection? = .natural) -> String {
Expand Down Expand Up @@ -71,6 +74,7 @@ class HTML {
{left: "$", right: "$", display: false},
]});
</script>
\(pluginScripts.joined(separator: " "))
</div>
</body>
</html>
Expand Down Expand Up @@ -105,6 +109,22 @@ class HTML {
baseCSS = cssContents
}

fileprivate func loadPluginScripts() {
guard let applicationSupportDirectory = Utils.getApplicationSupportDirectory(for: .plugins) else { return }

let contents = try? FileManager.default.contentsOfDirectory(
at: applicationSupportDirectory,
includingPropertiesForKeys: nil,
options: []
)

let scriptNames = contents?
.filter { $0.pathExtension == "js" }
.compactMap { $0.absoluteString.decoded } ?? []

self.pluginScripts = scriptNames.map { "<script src='\($0)'></script>" }
}

}

let html = HTML.shared
43 changes: 43 additions & 0 deletions Pine/Views/Preferences/PreviewStackView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// PreviewStackView.swift
// Pine
//
// Created by Luka Kerr on 24/3/19.
// Copyright © 2019 Luka Kerr. All rights reserved.
//

import Cocoa

class PreviewStackView: NSStackView, PreferenceStackView {

private func getPluginsArea() -> NSStackView {
let view = PreferencesStackView(name: "Plugins:")

let revealPluginsButton = PreferencesRoundedButton()
revealPluginsButton.title = "Reveal Plugins"
revealPluginsButton.target = self
revealPluginsButton.action = #selector(revealPlugins)

view.addPreferences([
revealPluginsButton
])

return view
}

public func getViews() -> [NSView] {
return [
getPluginsArea()
]
}

// MARK: - Plugins preference actions

@objc func revealPlugins(_ sender: NSButton) {
if let folder = Utils.getApplicationSupportDirectory(for: .plugins) {
NSWorkspace.shared.activateFileViewerSelecting([folder])
}
}

}

15 changes: 1 addition & 14 deletions Pine/Views/Preferences/UIStackView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,8 @@ class UIStackView: NSStackView, PreferenceStackView {

syntaxDropdown.selectItem(withTitle: theme.syntax)

let revealThemesButton = PreferencesRoundedButton()
revealThemesButton.title = "Reveal Themes"
revealThemesButton.target = self
revealThemesButton.action = #selector(revealThemes)

view.addPreferences([
syntaxDropdown,
revealThemesButton
syntaxDropdown
])

view.addBooleanArea(
Expand Down Expand Up @@ -97,13 +91,6 @@ class UIStackView: NSStackView, PreferenceStackView {
NotificationCenter.send(.preferencesChanged)
}

@objc func revealThemes(_ sender: NSButton) {
if let folder = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
let styles = folder.appendingPathComponent("highlight-js/styles")
NSWorkspace.shared.activateFileViewerSelecting([styles])
}
}

@objc func appearancePreferenceChanged(_ sender: NSButton) {
preferences.setFromBoolMap(appearanceMap, key: sender.title, value: sender.value)
}
Expand Down

0 comments on commit 014bd8c

Please sign in to comment.