├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── DarkImagePublishPlugin
│ └── DarkImagePublishPlugin.swift
├── Tests
├── DarkImagePublishPluginTests
│ ├── DarkImagePublishPluginTests.swift
│ └── XCTestManifests.swift
└── LinuxMain.swift
└── demo.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Guilherme Rambo
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | - Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 |
9 | - Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "codextended",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/johnsundell/codextended.git",
7 | "state" : {
8 | "revision" : "8d7c46dfc9c55240870cf5561d6cefa41e3d7105",
9 | "version" : "0.3.0"
10 | }
11 | },
12 | {
13 | "identity" : "collectionconcurrencykit",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/johnsundell/collectionConcurrencyKit.git",
16 | "state" : {
17 | "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
18 | "version" : "0.2.0"
19 | }
20 | },
21 | {
22 | "identity" : "files",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/johnsundell/files.git",
25 | "state" : {
26 | "revision" : "d273b5b7025d386feef79ef6bad7de762e106eaf",
27 | "version" : "4.2.0"
28 | }
29 | },
30 | {
31 | "identity" : "ink",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/johnsundell/ink.git",
34 | "state" : {
35 | "revision" : "77c3d8953374a9cf5418ef0bd7108524999de85a",
36 | "version" : "0.5.1"
37 | }
38 | },
39 | {
40 | "identity" : "plot",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/johnsundell/plot.git",
43 | "state" : {
44 | "revision" : "b358860fe565eb53e98b1f5807eb5939c8124547",
45 | "version" : "0.11.0"
46 | }
47 | },
48 | {
49 | "identity" : "publish",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/johnsundell/Publish.git",
52 | "state" : {
53 | "revision" : "1c8ad00d39c985cb5d497153241a2f1b654e0d40",
54 | "version" : "0.9.0"
55 | }
56 | },
57 | {
58 | "identity" : "shellout",
59 | "kind" : "remoteSourceControl",
60 | "location" : "https://github.com/johnsundell/shellout.git",
61 | "state" : {
62 | "revision" : "e1577acf2b6e90086d01a6d5e2b8efdaae033568",
63 | "version" : "2.3.0"
64 | }
65 | },
66 | {
67 | "identity" : "sweep",
68 | "kind" : "remoteSourceControl",
69 | "location" : "https://github.com/johnsundell/sweep.git",
70 | "state" : {
71 | "revision" : "801c2878e4c6c5baf32fe132e1f3f3af6f9fd1b0",
72 | "version" : "0.4.0"
73 | }
74 | }
75 | ],
76 | "version" : 2
77 | }
78 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.6
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "DarkImagePublishPlugin",
7 | platforms: [.macOS(.v12)],
8 | products: [
9 | .library(
10 | name: "DarkImagePublishPlugin",
11 | targets: ["DarkImagePublishPlugin"]
12 | )
13 | ],
14 | dependencies: [
15 | .package(url: "https://github.com/johnsundell/Publish.git", from: "0.9.0")
16 | ],
17 | targets: [
18 | .target(
19 | name: "DarkImagePublishPlugin",
20 | dependencies: ["Publish"]
21 | ),
22 | .testTarget(
23 | name: "DarkImagePublishPluginTests",
24 | dependencies: ["DarkImagePublishPlugin"]
25 | ),
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DarkImagePublishPlugin 😎
2 |
3 | A plugin for [Publish](https://github.com/JohnSundell/Publish) that lets you have two variants for images on your site: one for light mode, and one for dark mode. This is currently used in [rambo.codes](https://rambo.codes).
4 |
5 | 
6 |
7 | ## How to use it
8 |
9 | Just use the regular markdown syntax for images and the plugin takes care of the rest, so that the following markdown:
10 |
11 | ```markdown
12 | 
13 | ```
14 |
15 | Becomes this in HTML:
16 |
17 | ```html
18 |
19 |
20 |
21 |
22 |
23 |
24 | ```
25 |
26 | ## Installing the plugin
27 |
28 | To install the plugin, add it to your site's publishing steps:
29 |
30 | ```swift
31 | try mysite().publish(using: [
32 | .installPlugin(.darkImage()),
33 | // ...
34 | ])
35 | ```
36 |
37 | You can customize the suffix that's used for the dark variant by passing the `suffix` parameter:
38 |
39 | ```swift
40 | try mysite().publish(using: [
41 | .installPlugin(.darkImage(suffix: "bestmode")),
42 | // ...
43 | ])
44 | ```
45 |
46 | ## Light-only images
47 |
48 | In some cases, you might have just a single variant of an image, so trying to load the dark variant would fail. If you have an image with only a single variant, add the `?nodark` suffix to your image's path/URL:
49 |
50 | ```markdown
51 | 
52 | ```
53 |
54 | In that case, the generated HTML will look like this:
55 |
56 | ```html
57 |
58 |
59 |
60 |
61 |
62 | ```
--------------------------------------------------------------------------------
/Sources/DarkImagePublishPlugin/DarkImagePublishPlugin.swift:
--------------------------------------------------------------------------------
1 | /**
2 | * Dark image plugin for Publish
3 | * © 2020 Guilherme Rambo
4 | * BSD-2 license, see LICENSE file for details
5 | */
6 |
7 | import Publish
8 | import Ink
9 |
10 | public extension Plugin {
11 | static func darkImage(suffix: String = "-dark") -> Self {
12 | Plugin(name: "DarkImage") { context in
13 | context.markdownParser.addModifier(
14 | .darkImage(suffix: suffix)
15 | )
16 | }
17 | }
18 | }
19 |
20 | public extension Modifier {
21 | private static let noDarkMarker = "?nodark"
22 |
23 | static func darkImage(suffix: String) -> Self {
24 | return Modifier(target: .images) { html, markdown in
25 | let lightOnly = markdown.contains(Self.noDarkMarker)
26 | let input = markdown.replacingOccurrences(of: Self.noDarkMarker, with: "")
27 |
28 | let path = input.dropFirst("![".count).dropLast(")".count).drop(while: { $0 != "(" }).dropFirst()
29 |
30 | guard let dotIndex = path.lastIndex(of: ".") else { return html }
31 |
32 | var darkPath = path
33 | darkPath.insert(contentsOf: suffix, at: dotIndex)
34 |
35 | var altSuffix = ""
36 | if let alt = input.firstSubstring(between: "[", and: "]") {
37 | altSuffix = " alt=\"\(alt)\""
38 | }
39 |
40 | if lightOnly {
41 | return """
42 |
43 |
44 |
45 |
46 |
47 | """
48 | } else {
49 | return """
50 |
51 |
52 |
53 |
54 |
55 |
56 | """
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Tests/DarkImagePublishPluginTests/DarkImagePublishPluginTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import DarkImagePublishPlugin
3 | import Ink
4 |
5 | final class DarkImagePublishPluginTests: XCTestCase {
6 | func testGeneratingFigureTagsForImage() {
7 | let parser = MarkdownParser(modifiers: [.darkImage(suffix: "-dark")])
8 | let html = parser.html(from: "")
9 |
10 | XCTAssertEqual(html, """
11 |
12 |
13 |
14 |
15 |
16 |
17 | """)
18 | }
19 |
20 | func testNoDarkMarker() {
21 | let parser = MarkdownParser(modifiers: [.darkImage(suffix: "-dark")])
22 | let html = parser.html(from: "")
23 |
24 | XCTAssertEqual(html, """
25 |
26 |
27 |
28 |
29 |
30 | """)
31 | }
32 |
33 | static var allTests = [
34 | ("testGeneratingFigureTagsForImage", testGeneratingFigureTagsForImage),
35 | ("testNoDarkMarker", testNoDarkMarker),
36 | ]
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/Tests/DarkImagePublishPluginTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(DarkImagePublishPluginTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import DarkImagePublishPluginTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += DarkImagePublishPluginTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/DarkImagePublishPlugin/c5d6b3bbe4e434cad20cd67c3ff2c12b99e98f0b/demo.gif
--------------------------------------------------------------------------------