├── .codecov.yml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Package.resolved ├── Package.swift ├── Procfile ├── Public ├── robots.txt └── static │ ├── css │ ├── prismHighlighting.css │ └── style.css │ ├── images │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── broken-hands-logo_1.svg │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── manifest.json │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ ├── steampress-logo_1.png │ ├── steampress-og-image_1.jpg │ ├── steampress_1.png │ ├── vapor-oauth-logo_1.png │ └── vsh-logo_1.png │ └── js │ ├── admin │ ├── admin.js │ ├── createPost.js │ ├── createUser.js │ ├── login.js │ ├── punycode.js │ └── resetPassword.js │ ├── analytics.js │ ├── disqus.js │ ├── facebook.js │ ├── loadTweets.js │ ├── prismHighlighting.js │ └── search.js ├── README.md ├── Resources └── Views │ ├── 404.leaf │ ├── about.leaf │ ├── base.leaf │ ├── blog │ ├── admin │ │ ├── createPost.leaf │ │ ├── createUser.leaf │ │ ├── index.leaf │ │ ├── login.leaf │ │ └── resetPassword.leaf │ ├── authors.leaf │ ├── blog.leaf │ ├── post.leaf │ ├── postsView.leaf │ ├── profile.leaf │ ├── search.leaf │ ├── tag.leaf │ └── tags.leaf │ ├── footerAndScripts.leaf │ ├── headContent.leaf │ ├── index.leaf │ └── serverError.leaf ├── Sources ├── App │ ├── Models │ │ └── PostWithShortSnippet.swift │ ├── app.swift │ ├── configure.swift │ └── routes.swift └── Run │ └── main.swift ├── Tests └── AppTests │ └── SteamPressExampleTests.swift ├── resetDB.sh ├── setupDB.sh └── setupTestDB.sh /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "0...100" 3 | ignore: 4 | - "Tests/" 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | jobs: 5 | xenial: 6 | container: 7 | image: vapor3/swift:5.2-xenial 8 | services: 9 | psql: 10 | image: postgres 11 | ports: 12 | - 5432:5432 13 | env: 14 | POSTGRES_USER: steampress 15 | POSTGRES_DB: steampress-test 16 | POSTGRES_PASSWORD: password 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v1 20 | - run: swift test --enable-test-discovery --enable-code-coverage 21 | env: 22 | DATABASE_HOSTNAME: psql 23 | DATABASE_PORT: 5432 24 | bionic: 25 | container: 26 | image: vapor3/swift:5.2-bionic 27 | services: 28 | psql: 29 | image: postgres 30 | ports: 31 | - 5432:5432 32 | env: 33 | POSTGRES_USER: steampress 34 | POSTGRES_DB: steampress-test 35 | POSTGRES_PASSWORD: password 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v1 39 | - name: Run Bionic Tests 40 | run: swift test --enable-test-discovery --enable-code-coverage 41 | env: 42 | DATABASE_HOSTNAME: psql 43 | DATABASE_PORT: 5432 44 | - name: Setup container for codecov upload 45 | run: apt-get update && apt-get install curl -y 46 | - name: Process coverage file 47 | run: llvm-cov show .build/x86_64-unknown-linux-gnu/debug/SteamPressExamplePackageTests.xctest -instr-profile=.build/debug/codecov/default.profdata > coverage.txt 48 | - name: Upload code coverage 49 | uses: codecov/codecov-action@v1 50 | with: 51 | token: ${{ secrets.CODECOV_UPLOAD_KEY }} 52 | file: coverage.txt 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | xcuserdata 4 | *.xcodeproj 5 | Config/secrets 6 | DerivedData/ 7 | .swiftpm 8 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.2.1 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@brokenhands.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to a Broken Hands project 2 | 3 | :+1::tada: Thank you for wanting to contribute to this project! :tada::+1: 4 | 5 | We ask that you follow a few guidelines when contributing to one of our projects. 6 | 7 | ## Code of Conduct 8 | 9 | This project and everyone participating in it is governed by the [Broken Hands Code of Conduct](https://github.com/brokenhandsio/SteamPress/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [support@brokenhands.io](mailto:support@brokenhandsio). 10 | 11 | # How Can I Contribute? 12 | 13 | ### Reporting Bugs 14 | 15 | This section guides you through submitting a bug report for a Broken Hands project. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. 16 | 17 | Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). 18 | 19 | > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. 20 | 21 | #### Before Submitting A Bug Report 22 | 23 | * **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Abrokenhandsio)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. 24 | 25 | #### How Do I Submit A (Good) Bug Report? 26 | 27 | Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on the repository and provide the following information by filling in the issue form. 28 | 29 | Explain the problem and include additional details to help maintainers reproduce the problem: 30 | 31 | * **Use a clear and descriptive title** for the issue to identify the problem. 32 | * **Describe the exact steps which reproduce the problem** in as many details as possible. This usually means including some code, as well as __full__ error messages if applicable. 33 | * **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 34 | * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 35 | * **Explain which behavior you expected to see instead and why.** 36 | * **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below. 37 | 38 | ### Suggesting Enhancements 39 | 40 | This section guides you through submitting an enhancement suggestion for a Broken Hands project, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. 41 | 42 | Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in issue form, including the steps that you imagine you would take if the feature you're requesting existed. 43 | 44 | #### Before Submitting An Enhancement Suggestion 45 | 46 | * **Perform a [cursory search](https://github.com/issues?q=+is%3Aissue+user%3Abrokenhandsio)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. 47 | 48 | #### How Do I Submit A (Good) Enhancement Suggestion? 49 | 50 | Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue and provide the following information: 51 | 52 | * **Use a clear and descriptive title** for the issue to identify the suggestion. 53 | * **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 54 | * **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 55 | * **Describe the current behavior** and **explain which behavior you expected to see instead** and why. 56 | * **Explain why this enhancement would be useful** to other users and isn't something that can or should be implemented as a separate package. 57 | 58 | ### Pull Requests 59 | 60 | * Do not include issue numbers in the PR title 61 | * End all files with a newline 62 | * All new code should be run through `swiftlint` 63 | * All code must run on both Linux and macOS 64 | * All new code must be covered by tests 65 | * All bug fixes must be accompanied by a test which would fail if the bug fix was not implemented 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tanner Nelson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Auth", 6 | "repositoryURL": "https://github.com/vapor/auth.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "e6f61b403727ec124214beb3e57deff579f31d00", 10 | "version": "2.0.4" 11 | } 12 | }, 13 | { 14 | "package": "cmark", 15 | "repositoryURL": "https://github.com/brokenhandsio/cmark-gfm.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "c25ea288d0eb50c5c1150e7fdda675d5e9b1b6bb", 19 | "version": "2.0.0" 20 | } 21 | }, 22 | { 23 | "package": "Console", 24 | "repositoryURL": "https://github.com/vapor/console.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "74cfbea629d4aac34a97cead2447a6870af1950b", 28 | "version": "3.1.1" 29 | } 30 | }, 31 | { 32 | "package": "Core", 33 | "repositoryURL": "https://github.com/vapor/core.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "10d33362a47fab03a067e78fb0791341d9c634fa", 37 | "version": "3.9.3" 38 | } 39 | }, 40 | { 41 | "package": "Crypto", 42 | "repositoryURL": "https://github.com/vapor/crypto.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "df8eb7d8ae51787b3a0628aa3975e67666da936c", 46 | "version": "3.3.3" 47 | } 48 | }, 49 | { 50 | "package": "DatabaseKit", 51 | "repositoryURL": "https://github.com/vapor/database-kit.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4", 55 | "version": "1.3.3" 56 | } 57 | }, 58 | { 59 | "package": "Fluent", 60 | "repositoryURL": "https://github.com/vapor/fluent.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "783819d8838d15e1a05b459aa0fd1bde1e37ac26", 64 | "version": "3.2.1" 65 | } 66 | }, 67 | { 68 | "package": "FluentPostgreSQL", 69 | "repositoryURL": "https://github.com/vapor/fluent-postgres-driver.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "8e3eb9d24d54ac58c8d04c194ad6b24f0b1b667e", 73 | "version": "1.0.0" 74 | } 75 | }, 76 | { 77 | "package": "FluentSQLite", 78 | "repositoryURL": "https://github.com/vapor/fluent-sqlite.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "c32f5bda84bf4ea691d19afe183d40044f579e11", 82 | "version": "3.0.0" 83 | } 84 | }, 85 | { 86 | "package": "HTTP", 87 | "repositoryURL": "https://github.com/vapor/http.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "fba1329cd430e2f9a3f995b317b04f268d8b2978", 91 | "version": "3.3.2" 92 | } 93 | }, 94 | { 95 | "package": "Leaf", 96 | "repositoryURL": "https://github.com/vapor/leaf.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "d35f54cbac723e673f9bd5078361eea74049c8d7", 100 | "version": "3.0.2" 101 | } 102 | }, 103 | { 104 | "package": "LeafErrorMiddleware", 105 | "repositoryURL": "https://github.com/brokenhandsio/leaf-error-middleware.git", 106 | "state": { 107 | "branch": null, 108 | "revision": "ec012dacf7c59160a7a947b80dce06194da1f547", 109 | "version": "1.3.0" 110 | } 111 | }, 112 | { 113 | "package": "LeafMarkdown", 114 | "repositoryURL": "https://github.com/vapor-community/leaf-markdown.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "d451d3aeb7f44c4e5d46268b9ed99c51aefffef2", 118 | "version": "2.2.0" 119 | } 120 | }, 121 | { 122 | "package": "SwiftMarkdown", 123 | "repositoryURL": "https://github.com/vapor-community/markdown.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "43d56463f3c44cf4b705fe1b0b73b7dc7ff743b3", 127 | "version": "0.6.0" 128 | } 129 | }, 130 | { 131 | "package": "Multipart", 132 | "repositoryURL": "https://github.com/vapor/multipart.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "a7e0db512ff7eee7cd91d0a69dfeeec36f74d604", 136 | "version": "3.1.2" 137 | } 138 | }, 139 | { 140 | "package": "PostgreSQL", 141 | "repositoryURL": "https://github.com/vapor/postgresql.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "69ccf32e9164b207f9a773edac5433fe68ae3e5b", 145 | "version": "1.5.0" 146 | } 147 | }, 148 | { 149 | "package": "Routing", 150 | "repositoryURL": "https://github.com/vapor/routing.git", 151 | "state": { 152 | "branch": null, 153 | "revision": "d76f339c9716785e5079af9d7075d28ff7da3d92", 154 | "version": "3.1.0" 155 | } 156 | }, 157 | { 158 | "package": "Service", 159 | "repositoryURL": "https://github.com/vapor/service.git", 160 | "state": { 161 | "branch": null, 162 | "revision": "fa5b5de62bd68bcde9a69933f31319e46c7275fb", 163 | "version": "1.0.2" 164 | } 165 | }, 166 | { 167 | "package": "SQL", 168 | "repositoryURL": "https://github.com/vapor/sql.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "50eaeb8f52a1ce63f1ff3880e1114dd8757a78a6", 172 | "version": "2.3.2" 173 | } 174 | }, 175 | { 176 | "package": "SQLite", 177 | "repositoryURL": "https://github.com/vapor/sqlite.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "314d9cd21165bcf14215e336a23ff8214f40e411", 181 | "version": "3.2.1" 182 | } 183 | }, 184 | { 185 | "package": "SteamPress", 186 | "repositoryURL": "https://github.com/brokenhandsio/SteamPress.git", 187 | "state": { 188 | "branch": null, 189 | "revision": "56851f476abcb56c0ecf92dce1ce7e29044ac78a", 190 | "version": "1.0.0" 191 | } 192 | }, 193 | { 194 | "package": "SteampressFluentPostgres", 195 | "repositoryURL": "https://github.com/brokenhandsio/steampress-fluent-postgres.git", 196 | "state": { 197 | "branch": null, 198 | "revision": "65e66af97940b448e89255a5d63ed8571a6da46d", 199 | "version": "1.0.0" 200 | } 201 | }, 202 | { 203 | "package": "swift-nio", 204 | "repositoryURL": "https://github.com/apple/swift-nio.git", 205 | "state": { 206 | "branch": null, 207 | "revision": "8da5c5a4e6c5084c296b9f39dc54f00be146e0fa", 208 | "version": "1.14.2" 209 | } 210 | }, 211 | { 212 | "package": "swift-nio-ssl", 213 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", 214 | "state": { 215 | "branch": null, 216 | "revision": "0f3999f3e3c359cc74480c292644c3419e44a12f", 217 | "version": "1.4.0" 218 | } 219 | }, 220 | { 221 | "package": "swift-nio-ssl-support", 222 | "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", 223 | "state": { 224 | "branch": null, 225 | "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", 226 | "version": "1.0.0" 227 | } 228 | }, 229 | { 230 | "package": "swift-nio-zlib-support", 231 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", 232 | "state": { 233 | "branch": null, 234 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", 235 | "version": "1.0.0" 236 | } 237 | }, 238 | { 239 | "package": "SwiftSoup", 240 | "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", 241 | "state": { 242 | "branch": null, 243 | "revision": "7f187fd8804e63dea89879c95d7c2f7703727d0f", 244 | "version": "2.3.0" 245 | } 246 | }, 247 | { 248 | "package": "TemplateKit", 249 | "repositoryURL": "https://github.com/vapor/template-kit.git", 250 | "state": { 251 | "branch": null, 252 | "revision": "4370aa99c01fc19cc8272b67bf7204b2d2063680", 253 | "version": "1.5.0" 254 | } 255 | }, 256 | { 257 | "package": "URLEncodedForm", 258 | "repositoryURL": "https://github.com/vapor/url-encoded-form.git", 259 | "state": { 260 | "branch": null, 261 | "revision": "20f68fbe7fac006d4d0617ea4edcba033227359e", 262 | "version": "1.1.0" 263 | } 264 | }, 265 | { 266 | "package": "Validation", 267 | "repositoryURL": "https://github.com/vapor/validation.git", 268 | "state": { 269 | "branch": null, 270 | "revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6", 271 | "version": "2.1.1" 272 | } 273 | }, 274 | { 275 | "package": "Vapor", 276 | "repositoryURL": "https://github.com/vapor/vapor.git", 277 | "state": { 278 | "branch": null, 279 | "revision": "642f3d4d1f0eafad651c85524d0d1c698b55399f", 280 | "version": "3.3.3" 281 | } 282 | }, 283 | { 284 | "package": "VaporSecurityHeaders", 285 | "repositoryURL": "https://github.com/brokenhandsio/VaporSecurityHeaders.git", 286 | "state": { 287 | "branch": null, 288 | "revision": "9a01ec13f82a41d175f065ab0cef8fab6d0c08cd", 289 | "version": "2.1.1" 290 | } 291 | }, 292 | { 293 | "package": "WebSocket", 294 | "repositoryURL": "https://github.com/vapor/websocket.git", 295 | "state": { 296 | "branch": null, 297 | "revision": "d85e5b6dce4d04065865f77385fc3324f98178f6", 298 | "version": "1.1.2" 299 | } 300 | } 301 | ] 302 | }, 303 | "version": 1 304 | } 305 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SteamPressExample", 7 | products: [ 8 | .library(name: "App", targets: ["App"]), 9 | .executable(name: "Run", targets: ["Run"]) 10 | ], 11 | dependencies: [ 12 | .package(name: "Leaf", url: "https://github.com/vapor/leaf.git", from: "3.0.0"), 13 | .package(name: "LeafErrorMiddleware", url: "https://github.com/brokenhandsio/leaf-error-middleware.git", from: "1.2.0"), 14 | .package(url: "https://github.com/brokenhandsio/VaporSecurityHeaders.git", from: "2.0.0"), 15 | .package(name: "SteampressFluentPostgres", url: "https://github.com/brokenhandsio/steampress-fluent-postgres.git", from: "1.0.0"), 16 | .package(name: "LeafMarkdown", url: "https://github.com/vapor-community/leaf-markdown.git", from: "2.0.0") 17 | ], 18 | targets: [ 19 | .target(name: "App", 20 | dependencies: [ 21 | "Leaf", 22 | "LeafErrorMiddleware", 23 | "VaporSecurityHeaders", 24 | "SteampressFluentPostgres", 25 | "LeafMarkdown"]), 26 | .testTarget(name: "AppTests", dependencies: ["App"]), 27 | .target(name: "Run", dependencies: ["App"]) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: Run serve --env production --hostname 0.0.0.0 --port $PORT 2 | -------------------------------------------------------------------------------- /Public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /blog/admin/ 3 | Disallow: /blog/search/ 4 | -------------------------------------------------------------------------------- /Public/static/css/prismHighlighting.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+apacheconf+bash+c+csharp+cpp+ruby+css-extras+docker+git+go+graphql+groovy+http+java+json+kotlin+latex+less+makefile+markdown+nginx+objectivec+php+properties+protobuf+python+jsx+rust+sass+scss+swift+vim+wiki&plugins=line-numbers+autolinker */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | background: none; 12 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | pre.line-numbers { 125 | position: relative; 126 | padding-left: 3.8em; 127 | counter-reset: linenumber; 128 | } 129 | 130 | pre.line-numbers > code { 131 | position: relative; 132 | } 133 | 134 | .line-numbers .line-numbers-rows { 135 | position: absolute; 136 | pointer-events: none; 137 | top: 0; 138 | font-size: 100%; 139 | left: -3.8em; 140 | width: 3em; /* works for line-numbers below 1000 lines */ 141 | letter-spacing: -1px; 142 | border-right: 1px solid #999; 143 | 144 | -webkit-user-select: none; 145 | -moz-user-select: none; 146 | -ms-user-select: none; 147 | user-select: none; 148 | 149 | } 150 | 151 | .line-numbers-rows > span { 152 | pointer-events: none; 153 | display: block; 154 | counter-increment: linenumber; 155 | } 156 | 157 | .line-numbers-rows > span:before { 158 | content: counter(linenumber); 159 | color: #999; 160 | display: block; 161 | padding-right: 0.8em; 162 | text-align: right; 163 | } 164 | .token a { 165 | color: inherit; 166 | } 167 | -------------------------------------------------------------------------------- /Public/static/css/style.css: -------------------------------------------------------------------------------- 1 | /** Global **/ 2 | 3 | body { 4 | background-color: #346B8C; 5 | } 6 | 7 | #content { 8 | background-color: #ffffff; 9 | margin-top: 0px; 10 | padding-bottom: 2em; 11 | } 12 | 13 | footer { 14 | padding: 2rem 0; 15 | background-color: #346B8C; 16 | color: white; 17 | } 18 | 19 | .nav-link { 20 | color: white !important; 21 | } 22 | 23 | .nav-link:hover { 24 | color: #BDBDBD !important; 25 | } 26 | 27 | .navbar-brand { 28 | color: white !important; 29 | } 30 | 31 | #navigation { 32 | background-color: #346B8C; 33 | } 34 | 35 | .btn-primary { 36 | background-color: #DF7F81; 37 | border-color: #DF7F81; 38 | } 39 | 40 | .btn-primary:hover { 41 | background-color: #C57072; 42 | border-color: #C57072; 43 | } 44 | 45 | .btn-outline-primary { 46 | color: #DF7F81; 47 | border-color: #DF7F81; 48 | } 49 | 50 | .btn-outline-primary:hover { 51 | color: white; 52 | border-color: #DF7F81; 53 | background-color: #DF7F81; 54 | } 55 | 56 | .form-control-navbar { 57 | border: 1px solid #346B8C; 58 | } 59 | 60 | #github-navbar { 61 | font-size: 2em; 62 | } 63 | 64 | a { 65 | color: #DF7F81; 66 | } 67 | 68 | a:hover { 69 | color: #C57072; 70 | } 71 | 72 | #broken-hands-footer-logo { 73 | width: 1.5em; 74 | margin-right: 0.25em; 75 | } 76 | 77 | /** Global (end) **/ 78 | 79 | /** Homepage **/ 80 | 81 | #homepage-jumbotron { 82 | margin-bottom: 0; 83 | background-color: white; 84 | padding-top: 1em; 85 | padding-bottom: 1em; 86 | } 87 | 88 | #homepage-title-image { 89 | height: 20rem; 90 | max-width: 100%; 91 | } 92 | 93 | #homepage-lead { 94 | color: #DF7F81; 95 | } 96 | 97 | #homepage-footer-padder { 98 | margin-top: -2em; 99 | } 100 | 101 | #homepage-card-deck { 102 | margin-left: 1em; 103 | margin-right: 1em; 104 | } 105 | 106 | #open-source-copy { 107 | padding-left: 1em; 108 | padding-right: 1em; 109 | } 110 | 111 | .open-source-image { 112 | object-fit: scale-down; 113 | height: 10em; 114 | margin-top: 1em; 115 | } 116 | 117 | .index-post-link { 118 | color: rgb(41, 43, 44); 119 | text-align: center; 120 | } 121 | 122 | .index-post-link:hover { 123 | color: rgb(41, 43, 44); 124 | } 125 | 126 | .index-column h2 { 127 | text-align: center; 128 | } 129 | 130 | /** Homepage (end) **/ 131 | 132 | /** Blog **/ 133 | 134 | 135 | h1.lead-title-blog { 136 | font-size: 3.5rem; 137 | font-weight: 300; 138 | line-height: 1.1; 139 | } 140 | 141 | @media (min-width: 768px) { 142 | h1.lead-title-blog { 143 | font-size: 5.5rem; 144 | } 145 | } 146 | 147 | .post-contents img { 148 | margin-left: auto; 149 | margin-right: auto; 150 | display: block; 151 | max-width: 100%; 152 | } 153 | 154 | .post-contents { 155 | overflow-wrap: break-word; 156 | word-wrap: break-word; 157 | } 158 | 159 | .post-contents blockquote { 160 | background: #f7f7f7; 161 | padding: .5rem 1rem; 162 | padding-top: 1rem; 163 | margin-bottom: 1rem; 164 | } 165 | 166 | .post-title-link { 167 | color: rgb(41, 43, 44); 168 | } 169 | 170 | .post-title-link:hover { 171 | color: rgb(41, 43, 44); 172 | } 173 | 174 | .post-tags a { 175 | color: white; 176 | } 177 | 178 | .post-tags a:hover { 179 | text-decoration: none; 180 | } 181 | 182 | .social-media-buttons { 183 | width: 100%; 184 | display: flex; 185 | } 186 | 187 | .social-media-buttons div { 188 | flex-basis: 100% 189 | } 190 | 191 | #tag-title-badge { 192 | margin-left: .25em; 193 | } 194 | 195 | #profile-picture { 196 | max-width: 100% 197 | } 198 | 199 | .pagination .page-item.active .page-link { 200 | background-color: #DF7F81; 201 | border-color: #DF7F81; 202 | } 203 | 204 | .pagination .page-link { 205 | color: #DF7F81; 206 | } 207 | 208 | /** Blog (end) **/ 209 | 210 | /** Login/Reset Password **/ 211 | 212 | .form-signin { 213 | max-width: 330px; 214 | padding: 15px; 215 | margin: 0 auto; 216 | } 217 | 218 | .form-reset-password { 219 | max-width: 700px; 220 | padding: 15px; 221 | margin: 0 auto; 222 | } 223 | 224 | .form-reset-password label { 225 | padding-right: 0; 226 | } 227 | 228 | .form-signin .checkbox { 229 | font-weight: normal; 230 | margin-bottom: 10px; 231 | } 232 | .form-signin .form-control { 233 | position: relative; 234 | height: auto; 235 | -webkit-box-sizing: border-box; 236 | box-sizing: border-box; 237 | padding: 10px; 238 | font-size: 16px; 239 | } 240 | .form-signin .form-control:focus { 241 | z-index: 2; 242 | } 243 | .form-signin input[type="text"] { 244 | margin-bottom: -1px; 245 | border-bottom-right-radius: 0; 246 | border-bottom-left-radius: 0; 247 | } 248 | .form-signin input[type="password"] { 249 | margin-bottom: 10px; 250 | border-top-left-radius: 0; 251 | border-top-right-radius: 0; 252 | } 253 | 254 | .form-signin .form-group { 255 | margin-bottom: 0; 256 | } 257 | 258 | .form-reset-password { 259 | max-width: 500px; 260 | } 261 | 262 | /** Login/Reset Password (end) **/ 263 | 264 | /** Admin **/ 265 | 266 | #blog-admin-users td, #blog-admin-posts td { 267 | vertical-align:middle; 268 | } 269 | 270 | #inputUsername { 271 | text-transform: lowercase; 272 | } 273 | 274 | #blog-post-edit-title-warning { 275 | display: none; 276 | } 277 | 278 | .table-responsive { 279 | border: none; 280 | } 281 | 282 | /** Admin (end) **/ 283 | 284 | /** Search **/ 285 | 286 | #blog-search-input { 287 | width: 50%; 288 | } 289 | 290 | mark { 291 | background-color: rgba(223, 127, 129, 0.5); 292 | padding: 0px; 293 | } 294 | -------------------------------------------------------------------------------- /Public/static/images/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/android-chrome-192x192.png -------------------------------------------------------------------------------- /Public/static/images/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/android-chrome-512x512.png -------------------------------------------------------------------------------- /Public/static/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/apple-touch-icon.png -------------------------------------------------------------------------------- /Public/static/images/broken-hands-logo_1.svg: -------------------------------------------------------------------------------- 1 | logo-transparent-background -------------------------------------------------------------------------------- /Public/static/images/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #346b8c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Public/static/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/favicon-16x16.png -------------------------------------------------------------------------------- /Public/static/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/favicon-32x32.png -------------------------------------------------------------------------------- /Public/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/favicon.ico -------------------------------------------------------------------------------- /Public/static/images/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SteamPress", 3 | "icons": [ 4 | { 5 | "src": "/static/images/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "/static/images/android-chrome-512x512.png", 11 | "sizes": "512x512", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "background_color": "#ffffff", 17 | "display": "standalone" 18 | } -------------------------------------------------------------------------------- /Public/static/images/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/mstile-150x150.png -------------------------------------------------------------------------------- /Public/static/images/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Public/static/images/steampress-logo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/steampress-logo_1.png -------------------------------------------------------------------------------- /Public/static/images/steampress-og-image_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/steampress-og-image_1.jpg -------------------------------------------------------------------------------- /Public/static/images/steampress_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/steampress_1.png -------------------------------------------------------------------------------- /Public/static/images/vapor-oauth-logo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/vapor-oauth-logo_1.png -------------------------------------------------------------------------------- /Public/static/images/vsh-logo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brokenhandsio/SteamPressExample/5e413cacda3030b4c2172ef4e7a708294969459f/Public/static/images/vsh-logo_1.png -------------------------------------------------------------------------------- /Public/static/js/admin/admin.js: -------------------------------------------------------------------------------- 1 | $('.delete-user-button').click(function(){ 2 | return confirm('Are you sure you want to delete this user?'); 3 | }); 4 | 5 | $('.delete-post-button').click(function(){ 6 | return confirm('Are you sure you want to delete this post?'); 7 | }); 8 | -------------------------------------------------------------------------------- /Public/static/js/admin/createPost.js: -------------------------------------------------------------------------------- 1 | $("#create-post-form").on('submit', function() { 2 | var title = $("#title").val(); 3 | var contents = $("#contents").val(); 4 | 5 | if (!title) { 6 | alert("Please enter a title"); 7 | return false; 8 | } 9 | 10 | if (!contents) { 11 | alert("Please enter some contents"); 12 | return false; 13 | } 14 | return true; 15 | }); 16 | 17 | 18 | var editingPost = false; 19 | var published = false; 20 | var allowEditingOfSlugUrl = true; 21 | var originalSlugUrl = ""; 22 | var originalTitle = ""; 23 | 24 | $(function() { 25 | var simplemde = new SimpleMDE({ 26 | element: $("#contents")[0], 27 | spellChecker: false, 28 | forceSync: true, 29 | placeholder: "Write your blog post here", 30 | autosave: { 31 | enabled: true, 32 | uniqueId: "SteamPress-Create-Post", 33 | delay: 1000, 34 | }, 35 | promptURLs: true, 36 | }); 37 | // SimpleMDE has been initialised so we need to turn off validation for the 38 | // underlying text area 39 | $('#contents').removeAttr('required'); 40 | }); 41 | 42 | $('#title').on('input',function(e){ 43 | if (allowEditingOfSlugUrl) { 44 | var title = $('#title').val(); 45 | var slugUrl = slugify(title); 46 | $('#slugUrl').val(slugUrl); 47 | if (editingPost && published) { 48 | if (title != originalTitle) { 49 | $('#blog-post-edit-title-warning').fadeIn(); 50 | } 51 | else { 52 | $('#blog-post-edit-title-warning').fadeOut(); 53 | } 54 | } 55 | } 56 | }); 57 | 58 | $.ajax({ 59 | url: "/blog/api/tags/", 60 | type: 'GET', 61 | contentType: 'application/json; charset=utf-8' 62 | }).then(function (response) { 63 | var dataToReturn = []; 64 | for (var i=0; i < response.length; i++) { 65 | var tagToTransform = response[i]; 66 | var newTag = {id: tagToTransform["name"], text: tagToTransform["name"]}; 67 | dataToReturn.push(newTag); 68 | } 69 | $("#tags").select2({ 70 | placeholder: "Select Tags for the Blog Post", 71 | tags: true, 72 | tokenSeparators: [','], 73 | data: dataToReturn 74 | }); 75 | }); 76 | 77 | $('#cancel-edit-button').click(function(){ 78 | return confirm('Are you sure you want to cancel? You will lose any unsaved work'); 79 | }); 80 | 81 | $('#keep-original-slug-url-link').click(function(){ 82 | keepPostOriginalSlugUrl(); 83 | }); 84 | 85 | $(function() { 86 | if ($("#edit-post-data").length) { 87 | editingPost = true; 88 | originalSlugUrl = $("#edit-post-data").data("originalSlugUrl"); 89 | originalTitle = $("#edit-post-data").data("originalTitle"); 90 | published = $("#edit-post-data").data("publishedPost"); 91 | } 92 | }); 93 | 94 | function slugify(text) 95 | { 96 | return punycode.toASCII(text).toString().toLowerCase() 97 | .replace(/\s+/g, '-') // Replace spaces with - 98 | .replace(/[^\w\-]+/g, '') // Remove all non-word chars 99 | .replace(/\-\-+/g, '-') // Replace multiple - with single - 100 | .replace(/^-+/, '') // Trim - from start of text 101 | .replace(/-+$/, ''); // Trim - from end of text 102 | } 103 | 104 | function keepPostOriginalSlugUrl() { 105 | allowEditingOfSlugUrl = false; 106 | $('#slugUrl').val(originalSlugUrl); 107 | $('#blog-post-edit-title-warning').alert('close') 108 | } 109 | -------------------------------------------------------------------------------- /Public/static/js/admin/createUser.js: -------------------------------------------------------------------------------- 1 | var editing = false; 2 | 3 | $(function() { 4 | editing = $("#edit-user-data").data("editingPage"); 5 | }); 6 | 7 | $('#cancel-edit-button').click(function(){ 8 | return confirm('Are you sure you want to cancel? You will lose any unsaved work'); 9 | }); 10 | 11 | $("#create-user-form").on('submit', function() { 12 | var name = $("#name").val(); 13 | var username = $("#username").val(); 14 | var password = $("#password").val(); 15 | var confirm = $("#confirmPassword").val(); 16 | 17 | if (!isValidName(name)) { 18 | alert("Please enter a valid name"); 19 | return false; 20 | } 21 | 22 | if (!isValidUsername(username)) { 23 | alert("Please enter a valid username"); 24 | return false; 25 | } 26 | 27 | if (!editing || password !== "") { 28 | if (!isValidPassword(password)) { 29 | alert("Please enter a valid password"); 30 | return false; 31 | } 32 | 33 | if (password != confirm) { 34 | alert("Please ensure your passwords match") 35 | return false; 36 | } 37 | } 38 | return true; 39 | }); 40 | 41 | $("#name").blur(function() { 42 | var name = $("#name").val(); 43 | if (isValidName(name)) { 44 | $("#name").removeClass("is-invalid"); 45 | } 46 | else { 47 | $("#name").addClass("is-invalid"); 48 | } 49 | }); 50 | 51 | $("#username").on('change keyup paste',function(){ 52 | $(this).val($(this).val().toLowerCase()); 53 | }) 54 | 55 | $("#username").blur(function() { 56 | var username = $("#username").val(); 57 | if (isValidUsername(username)) { 58 | $("#username").removeClass("is-invalid"); 59 | } 60 | else { 61 | $("#username").addClass("is-invalid"); 62 | } 63 | }); 64 | 65 | $("#password").blur(function() { 66 | var password = $("#password").val(); 67 | if (editing && !password) { 68 | return; 69 | } 70 | if (isValidPassword(password)) { 71 | $("#password").removeClass("is-invalid"); 72 | $("#password-feedback").hide(); 73 | } 74 | else { 75 | $("#password").addClass("is-invalid"); 76 | $("#password-feedback").show(); 77 | } 78 | }); 79 | 80 | $("#confirmPassword").blur(function() { 81 | var password = $("#password").val(); 82 | var confirm = $("#confirmPassword").val(); 83 | if (editing && !password && !confirm) { 84 | return; 85 | } 86 | if (password == confirm) { 87 | $("#confirmPassword").removeClass("is-invalid"); 88 | } 89 | else { 90 | $("#confirmPassword").addClass("is-invalid"); 91 | } 92 | }); 93 | 94 | function isValidName(name) { 95 | if (name) { 96 | if (name.length > 64) { 97 | return false; 98 | } 99 | if (/^[a-zA-Z \-.,']+$/.test(name)) { 100 | return true; 101 | } 102 | return false; 103 | } 104 | return false; 105 | } 106 | 107 | function isValidUsername(username) { 108 | if (username) { 109 | if (username.length > 64) { 110 | return false; 111 | } 112 | if (/^[a-z0-9\-.]+$/.test(username.toLowerCase())) { 113 | return true; 114 | } 115 | return false; 116 | } 117 | return false; 118 | } 119 | 120 | function isValidPassword(password) { 121 | if (editing && !password) { 122 | return true; 123 | } 124 | 125 | if (!password) { 126 | return false; 127 | } 128 | 129 | if (password.length < 8) { 130 | return false; 131 | } 132 | 133 | var anUpperCase = /[A-Z]/; 134 | var aLowerCase = /[a-z]/; 135 | var aNumber = /[0-9]/; 136 | var aSpecial = /[!|@|#|$|%|^|&|*|(|)|-|_|']/; 137 | 138 | var complexity = 0; 139 | for(var i=0; i= 0x80 (not a basic code point)', 27 | 'invalid-input': 'Invalid input' 28 | }; 29 | 30 | /** Convenience shortcuts */ 31 | const baseMinusTMin = base - tMin; 32 | const floor = Math.floor; 33 | const stringFromCharCode = String.fromCharCode; 34 | 35 | /*--------------------------------------------------------------------------*/ 36 | 37 | /** 38 | * A generic error utility function. 39 | * @private 40 | * @param {String} type The error type. 41 | * @returns {Error} Throws a `RangeError` with the applicable error message. 42 | */ 43 | function error(type) { 44 | throw new RangeError(errors[type]); 45 | } 46 | 47 | /** 48 | * A generic `Array#map` utility function. 49 | * @private 50 | * @param {Array} array The array to iterate over. 51 | * @param {Function} callback The function that gets called for every array 52 | * item. 53 | * @returns {Array} A new array of values returned by the callback function. 54 | */ 55 | function map(array, fn) { 56 | const result = []; 57 | let length = array.length; 58 | while (length--) { 59 | result[length] = fn(array[length]); 60 | } 61 | return result; 62 | } 63 | 64 | /** 65 | * A simple `Array#map`-like wrapper to work with domain name strings or email 66 | * addresses. 67 | * @private 68 | * @param {String} domain The domain name or email address. 69 | * @param {Function} callback The function that gets called for every 70 | * character. 71 | * @returns {Array} A new string of characters returned by the callback 72 | * function. 73 | */ 74 | function mapDomain(string, fn) { 75 | const parts = string.split('@'); 76 | let result = ''; 77 | if (parts.length > 1) { 78 | // In email addresses, only the domain name should be punycoded. Leave 79 | // the local part (i.e. everything up to `@`) intact. 80 | result = parts[0] + '@'; 81 | string = parts[1]; 82 | } 83 | // Avoid `split(regex)` for IE8 compatibility. See #17. 84 | string = string.replace(regexSeparators, '\x2E'); 85 | const labels = string.split('.'); 86 | const encoded = map(labels, fn).join('.'); 87 | return result + encoded; 88 | } 89 | 90 | /** 91 | * Creates an array containing the numeric code points of each Unicode 92 | * character in the string. While JavaScript uses UCS-2 internally, 93 | * this function will convert a pair of surrogate halves (each of which 94 | * UCS-2 exposes as separate characters) into a single code point, 95 | * matching UTF-16. 96 | * @see `punycode.ucs2.encode` 97 | * @see 98 | * @memberOf punycode.ucs2 99 | * @name decode 100 | * @param {String} string The Unicode input string (UCS-2). 101 | * @returns {Array} The new array of code points. 102 | */ 103 | function ucs2decode(string) { 104 | const output = []; 105 | let counter = 0; 106 | const length = string.length; 107 | while (counter < length) { 108 | const value = string.charCodeAt(counter++); 109 | if (value >= 0xD800 && value <= 0xDBFF && counter < length) { 110 | // It's a high surrogate, and there is a next character. 111 | const extra = string.charCodeAt(counter++); 112 | if ((extra & 0xFC00) == 0xDC00) { // Low surrogate. 113 | output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); 114 | } else { 115 | // It's an unmatched surrogate; only append this code unit, in case the 116 | // next code unit is the high surrogate of a surrogate pair. 117 | output.push(value); 118 | counter--; 119 | } 120 | } else { 121 | output.push(value); 122 | } 123 | } 124 | return output; 125 | } 126 | 127 | /** 128 | * Creates a string based on an array of numeric code points. 129 | * @see `punycode.ucs2.decode` 130 | * @memberOf punycode.ucs2 131 | * @name encode 132 | * @param {Array} codePoints The array of numeric code points. 133 | * @returns {String} The new Unicode string (UCS-2). 134 | */ 135 | const ucs2encode = array => String.fromCodePoint(...array); 136 | 137 | /** 138 | * Converts a basic code point into a digit/integer. 139 | * @see `digitToBasic()` 140 | * @private 141 | * @param {Number} codePoint The basic numeric code point value. 142 | * @returns {Number} The numeric value of a basic code point (for use in 143 | * representing integers) in the range `0` to `base - 1`, or `base` if 144 | * the code point does not represent a value. 145 | */ 146 | const basicToDigit = function(codePoint) { 147 | if (codePoint - 0x30 < 0x0A) { 148 | return codePoint - 0x16; 149 | } 150 | if (codePoint - 0x41 < 0x1A) { 151 | return codePoint - 0x41; 152 | } 153 | if (codePoint - 0x61 < 0x1A) { 154 | return codePoint - 0x61; 155 | } 156 | return base; 157 | }; 158 | 159 | /** 160 | * Converts a digit/integer into a basic code point. 161 | * @see `basicToDigit()` 162 | * @private 163 | * @param {Number} digit The numeric value of a basic code point. 164 | * @returns {Number} The basic code point whose value (when used for 165 | * representing integers) is `digit`, which needs to be in the range 166 | * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is 167 | * used; else, the lowercase form is used. The behavior is undefined 168 | * if `flag` is non-zero and `digit` has no uppercase form. 169 | */ 170 | const digitToBasic = function(digit, flag) { 171 | // 0..25 map to ASCII a..z or A..Z 172 | // 26..35 map to ASCII 0..9 173 | return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); 174 | }; 175 | 176 | /** 177 | * Bias adaptation function as per section 3.4 of RFC 3492. 178 | * https://tools.ietf.org/html/rfc3492#section-3.4 179 | * @private 180 | */ 181 | const adapt = function(delta, numPoints, firstTime) { 182 | let k = 0; 183 | delta = firstTime ? floor(delta / damp) : delta >> 1; 184 | delta += floor(delta / numPoints); 185 | for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { 186 | delta = floor(delta / baseMinusTMin); 187 | } 188 | return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); 189 | }; 190 | 191 | /** 192 | * Converts a Punycode string of ASCII-only symbols to a string of Unicode 193 | * symbols. 194 | * @memberOf punycode 195 | * @param {String} input The Punycode string of ASCII-only symbols. 196 | * @returns {String} The resulting string of Unicode symbols. 197 | */ 198 | const decode = function(input) { 199 | // Don't use UCS-2. 200 | const output = []; 201 | const inputLength = input.length; 202 | let i = 0; 203 | let n = initialN; 204 | let bias = initialBias; 205 | 206 | // Handle the basic code points: let `basic` be the number of input code 207 | // points before the last delimiter, or `0` if there is none, then copy 208 | // the first basic code points to the output. 209 | 210 | let basic = input.lastIndexOf(delimiter); 211 | if (basic < 0) { 212 | basic = 0; 213 | } 214 | 215 | for (let j = 0; j < basic; ++j) { 216 | // if it's not a basic code point 217 | if (input.charCodeAt(j) >= 0x80) { 218 | error('not-basic'); 219 | } 220 | output.push(input.charCodeAt(j)); 221 | } 222 | 223 | // Main decoding loop: start just after the last delimiter if any basic code 224 | // points were copied; start at the beginning otherwise. 225 | 226 | for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { 227 | 228 | // `index` is the index of the next character to be consumed. 229 | // Decode a generalized variable-length integer into `delta`, 230 | // which gets added to `i`. The overflow checking is easier 231 | // if we increase `i` as we go, then subtract off its starting 232 | // value at the end to obtain `delta`. 233 | let oldi = i; 234 | for (let w = 1, k = base; /* no condition */; k += base) { 235 | 236 | if (index >= inputLength) { 237 | error('invalid-input'); 238 | } 239 | 240 | const digit = basicToDigit(input.charCodeAt(index++)); 241 | 242 | if (digit >= base || digit > floor((maxInt - i) / w)) { 243 | error('overflow'); 244 | } 245 | 246 | i += digit * w; 247 | const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 248 | 249 | if (digit < t) { 250 | break; 251 | } 252 | 253 | const baseMinusT = base - t; 254 | if (w > floor(maxInt / baseMinusT)) { 255 | error('overflow'); 256 | } 257 | 258 | w *= baseMinusT; 259 | 260 | } 261 | 262 | const out = output.length + 1; 263 | bias = adapt(i - oldi, out, oldi == 0); 264 | 265 | // `i` was supposed to wrap around from `out` to `0`, 266 | // incrementing `n` each time, so we'll fix that now: 267 | if (floor(i / out) > maxInt - n) { 268 | error('overflow'); 269 | } 270 | 271 | n += floor(i / out); 272 | i %= out; 273 | 274 | // Insert `n` at position `i` of the output. 275 | output.splice(i++, 0, n); 276 | 277 | } 278 | 279 | return String.fromCodePoint(...output); 280 | }; 281 | 282 | /** 283 | * Converts a string of Unicode symbols (e.g. a domain name label) to a 284 | * Punycode string of ASCII-only symbols. 285 | * @memberOf punycode 286 | * @param {String} input The string of Unicode symbols. 287 | * @returns {String} The resulting Punycode string of ASCII-only symbols. 288 | */ 289 | const encode = function(input) { 290 | const output = []; 291 | 292 | // Convert the input in UCS-2 to an array of Unicode code points. 293 | input = ucs2decode(input); 294 | 295 | // Cache the length. 296 | let inputLength = input.length; 297 | 298 | // Initialize the state. 299 | let n = initialN; 300 | let delta = 0; 301 | let bias = initialBias; 302 | 303 | // Handle the basic code points. 304 | for (const currentValue of input) { 305 | if (currentValue < 0x80) { 306 | output.push(stringFromCharCode(currentValue)); 307 | } 308 | } 309 | 310 | let basicLength = output.length; 311 | let handledCPCount = basicLength; 312 | 313 | // `handledCPCount` is the number of code points that have been handled; 314 | // `basicLength` is the number of basic code points. 315 | 316 | // Finish the basic string with a delimiter unless it's empty. 317 | if (basicLength) { 318 | output.push(delimiter); 319 | } 320 | 321 | // Main encoding loop: 322 | while (handledCPCount < inputLength) { 323 | 324 | // All non-basic code points < n have been handled already. Find the next 325 | // larger one: 326 | let m = maxInt; 327 | for (const currentValue of input) { 328 | if (currentValue >= n && currentValue < m) { 329 | m = currentValue; 330 | } 331 | } 332 | 333 | // Increase `delta` enough to advance the decoder's state to , 334 | // but guard against overflow. 335 | const handledCPCountPlusOne = handledCPCount + 1; 336 | if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { 337 | error('overflow'); 338 | } 339 | 340 | delta += (m - n) * handledCPCountPlusOne; 341 | n = m; 342 | 343 | for (const currentValue of input) { 344 | if (currentValue < n && ++delta > maxInt) { 345 | error('overflow'); 346 | } 347 | if (currentValue == n) { 348 | // Represent delta as a generalized variable-length integer. 349 | let q = delta; 350 | for (let k = base; /* no condition */; k += base) { 351 | const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 352 | if (q < t) { 353 | break; 354 | } 355 | const qMinusT = q - t; 356 | const baseMinusT = base - t; 357 | output.push( 358 | stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) 359 | ); 360 | q = floor(qMinusT / baseMinusT); 361 | } 362 | 363 | output.push(stringFromCharCode(digitToBasic(q, 0))); 364 | bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); 365 | delta = 0; 366 | ++handledCPCount; 367 | } 368 | } 369 | 370 | ++delta; 371 | ++n; 372 | 373 | } 374 | return output.join(''); 375 | }; 376 | 377 | /** 378 | * Converts a Punycode string representing a domain name or an email address 379 | * to Unicode. Only the Punycoded parts of the input will be converted, i.e. 380 | * it doesn't matter if you call it on a string that has already been 381 | * converted to Unicode. 382 | * @memberOf punycode 383 | * @param {String} input The Punycoded domain name or email address to 384 | * convert to Unicode. 385 | * @returns {String} The Unicode representation of the given Punycode 386 | * string. 387 | */ 388 | const toUnicode = function(input) { 389 | return mapDomain(input, function(string) { 390 | return regexPunycode.test(string) 391 | ? decode(string.slice(4).toLowerCase()) 392 | : string; 393 | }); 394 | }; 395 | 396 | /** 397 | * Converts a Unicode string representing a domain name or an email address to 398 | * Punycode. Only the non-ASCII parts of the domain name will be converted, 399 | * i.e. it doesn't matter if you call it with a domain that's already in 400 | * ASCII. 401 | * @memberOf punycode 402 | * @param {String} input The domain name or email address to convert, as a 403 | * Unicode string. 404 | * @returns {String} The Punycode representation of the given domain name or 405 | * email address. 406 | */ 407 | const toASCII = function(input) { 408 | return mapDomain(input, function(string) { 409 | return regexNonASCII.test(string) 410 | ? 'xn--' + encode(string) 411 | : string; 412 | }); 413 | }; 414 | 415 | /*--------------------------------------------------------------------------*/ 416 | 417 | /** Define the public API */ 418 | const punycode = { 419 | /** 420 | * A string representing the current Punycode.js version number. 421 | * @memberOf punycode 422 | * @type String 423 | */ 424 | 'version': '2.1.0', 425 | /** 426 | * An object of methods to convert from JavaScript's internal character 427 | * representation (UCS-2) to Unicode code points, and back. 428 | * @see 429 | * @memberOf punycode 430 | * @type Object 431 | */ 432 | 'ucs2': { 433 | 'decode': ucs2decode, 434 | 'encode': ucs2encode 435 | }, 436 | 'decode': decode, 437 | 'encode': encode, 438 | 'toASCII': toASCII, 439 | 'toUnicode': toUnicode 440 | }; 441 | 442 | // module.exports = punycode; 443 | -------------------------------------------------------------------------------- /Public/static/js/admin/resetPassword.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | window.addEventListener("load", function() { 4 | var form = document.getElementById("reset-password-form"); 5 | form.addEventListener("submit", function(event) { 6 | if (form.checkValidity() == false) { 7 | event.preventDefault(); 8 | event.stopPropagation(); 9 | } 10 | form.classList.add("was-validated"); 11 | }, false); 12 | }, false); 13 | }()); 14 | 15 | $("#reset-password-form").on('submit', function() { 16 | var password = $("#inputPassword").val(); 17 | var confirmPassword = $("#inputConfirmPassword").val(); 18 | 19 | if (!isValidPassword(password) || password != confirmPassword) { 20 | return false; 21 | } 22 | 23 | return true; 24 | }); 25 | 26 | $("#inputPassword").blur(function() { 27 | var password = $("#inputPassword").val(); 28 | if (isValidPassword(password)) { 29 | $("#inputPassword").removeClass("is-invalid"); 30 | $("#password-feedback").hide(); 31 | } 32 | else { 33 | $("#inputPassword").removeClass("is-valid"); 34 | } 35 | }); 36 | 37 | $("#inputConfirmPassword").blur(function() { 38 | var password = $("#inputPassword").val(); 39 | var confirm = $("#inputConfirmPassword").val(); 40 | if (password == confirm) { 41 | $("#inputConfirmPassword").removeClass("is-invalid"); 42 | $("#confirm-password-feedback").hide(); 43 | } 44 | else { 45 | $("#inputConfirmPassword").addClass("is-invalid"); 46 | $("#confirm-password-feedback").show(); 47 | } 48 | }); 49 | 50 | function isValidPassword(password) { 51 | if (!password) { 52 | return false; 53 | } 54 | 55 | if (password.length < 8) { 56 | return false; 57 | } 58 | 59 | return true; 60 | } 61 | -------------------------------------------------------------------------------- /Public/static/js/analytics.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 5 | 6 | var gaIdentifier = document.getElementById("google-analytics-data").dataset.identifier; 7 | ga('create', gaIdentifier, 'auto'); 8 | ga('send', 'pageview'); 9 | -------------------------------------------------------------------------------- /Public/static/js/disqus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS. 3 | * LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/ 4 | 5 | var disqus_config = function () { 6 | this.page.identifier = $("#blog-post-data").data("postIdentifier"); 7 | }; 8 | 9 | (function() { // DON'T EDIT BELOW THIS LINE 10 | var d = document, s = d.createElement('script'); 11 | var disqusName = $("#blog-post-data").data("disqusName"); 12 | s.src = 'https://' + disqusName + '.disqus.com/embed.js'; 13 | s.setAttribute('data-timestamp', +new Date()); 14 | (d.head || d.body).appendChild(s); 15 | })(); 16 | -------------------------------------------------------------------------------- /Public/static/js/facebook.js: -------------------------------------------------------------------------------- 1 | (function(d, s, id) { 2 | var js, fjs = d.getElementsByTagName(s)[0]; 3 | if (d.getElementById(id)) return; 4 | js = d.createElement(s); js.id = id; 5 | js.src = "https://connect.facebook.net/en_GB/sdk.js#xfbml=1&version=v2.8"; 6 | fjs.parentNode.insertBefore(js, fjs); 7 | }(document, 'script', 'facebook-jssdk')); 8 | -------------------------------------------------------------------------------- /Public/static/js/loadTweets.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $('a', $('.post-contents')).each(function () { 3 | var link = this; 4 | var url = $(link).attr('href'); 5 | if (url.indexOf('https://twitter.com') === 0 && url.indexOf('status') > 0) { 6 | $.ajax({ 7 | url: 'https://publish.twitter.com/oembed?url=' + url + '&align=center', 8 | crossDomain: true, 9 | dataType: 'jsonp' 10 | }) 11 | .done(function (data) { 12 | $(link).after(data.html).remove(); 13 | }); 14 | }; 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /Public/static/js/prismHighlighting.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+apacheconf+bash+c+csharp+cpp+ruby+css-extras+docker+git+go+graphql+groovy+http+java+json+kotlin+latex+less+makefile+markdown+nginx+objectivec+php+properties+protobuf+python+jsx+rust+sass+scss+swift+vim+wiki&plugins=line-numbers+autolinker */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){h.lastIndex=0;var _=h.exec(w),P=1;if(!_&&m&&b!=t.length-1){if(h.lastIndex=k,_=h.exec(e),!_)break;for(var A=_.index+(d?_[1].length:0),j=_.index+_[0].length,x=b,O=k,S=t.length;S>x&&(j>O||!t[x].type&&!t[x-1].greedy);++x)O+=t[x].length,A>=O&&(++b,k=O);if(t[b]instanceof s||t[x-1].greedy)continue;P=x-b,w=e.slice(k,O),_.index-=k}if(_){d&&(p=_[1].length);var A=_.index+p,_=_[0].slice(p),j=A+_.length,N=w.slice(0,A),C=w.slice(j),E=[b,P];N&&(++b,k+=N.length,E.push(N));var L=new s(u,f?n.tokenize(_,f):_,y,_,m);if(E.push(L),C&&E.push(C),Array.prototype.splice.apply(t,E),1!=P&&n.matchGrammar(e,t,a,b,k,!0,u),l)break}else if(l)break}}}}},tokenize:function(e,t){var a=[e],r=t.rest;if(r){for(var i in r)t[i]=r[i];delete t.rest}return n.matchGrammar(e,a,t,0,0,!1),a},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o=Object.keys(i.attributes).map(function(e){return e+'="'+(i.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\s\S])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\s\S]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 4 | Prism.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\s\S]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[[^\]\r\n]+]|\\.|[^\/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; 7 | Prism.languages.apacheconf={comment:/#.*/,"directive-inline":{pattern:/^(\s*)\b(AcceptFilter|AcceptPathInfo|AccessFileName|Action|AddAlt|AddAltByEncoding|AddAltByType|AddCharset|AddDefaultCharset|AddDescription|AddEncoding|AddHandler|AddIcon|AddIconByEncoding|AddIconByType|AddInputFilter|AddLanguage|AddModuleInfo|AddOutputFilter|AddOutputFilterByType|AddType|Alias|AliasMatch|Allow|AllowCONNECT|AllowEncodedSlashes|AllowMethods|AllowOverride|AllowOverrideList|Anonymous|Anonymous_LogEmail|Anonymous_MustGiveEmail|Anonymous_NoUserID|Anonymous_VerifyEmail|AsyncRequestWorkerFactor|AuthBasicAuthoritative|AuthBasicFake|AuthBasicProvider|AuthBasicUseDigestAlgorithm|AuthDBDUserPWQuery|AuthDBDUserRealmQuery|AuthDBMGroupFile|AuthDBMType|AuthDBMUserFile|AuthDigestAlgorithm|AuthDigestDomain|AuthDigestNonceLifetime|AuthDigestProvider|AuthDigestQop|AuthDigestShmemSize|AuthFormAuthoritative|AuthFormBody|AuthFormDisableNoStore|AuthFormFakeBasicAuth|AuthFormLocation|AuthFormLoginRequiredLocation|AuthFormLoginSuccessLocation|AuthFormLogoutLocation|AuthFormMethod|AuthFormMimetype|AuthFormPassword|AuthFormProvider|AuthFormSitePassphrase|AuthFormSize|AuthFormUsername|AuthGroupFile|AuthLDAPAuthorizePrefix|AuthLDAPBindAuthoritative|AuthLDAPBindDN|AuthLDAPBindPassword|AuthLDAPCharsetConfig|AuthLDAPCompareAsUser|AuthLDAPCompareDNOnServer|AuthLDAPDereferenceAliases|AuthLDAPGroupAttribute|AuthLDAPGroupAttributeIsDN|AuthLDAPInitialBindAsUser|AuthLDAPInitialBindPattern|AuthLDAPMaxSubGroupDepth|AuthLDAPRemoteUserAttribute|AuthLDAPRemoteUserIsDN|AuthLDAPSearchAsUser|AuthLDAPSubGroupAttribute|AuthLDAPSubGroupClass|AuthLDAPUrl|AuthMerging|AuthName|AuthnCacheContext|AuthnCacheEnable|AuthnCacheProvideFor|AuthnCacheSOCache|AuthnCacheTimeout|AuthnzFcgiCheckAuthnProvider|AuthnzFcgiDefineProvider|AuthType|AuthUserFile|AuthzDBDLoginToReferer|AuthzDBDQuery|AuthzDBDRedirectQuery|AuthzDBMType|AuthzSendForbiddenOnFailure|BalancerGrowth|BalancerInherit|BalancerMember|BalancerPersist|BrowserMatch|BrowserMatchNoCase|BufferedLogs|BufferSize|CacheDefaultExpire|CacheDetailHeader|CacheDirLength|CacheDirLevels|CacheDisable|CacheEnable|CacheFile|CacheHeader|CacheIgnoreCacheControl|CacheIgnoreHeaders|CacheIgnoreNoLastMod|CacheIgnoreQueryString|CacheIgnoreURLSessionIdentifiers|CacheKeyBaseURL|CacheLastModifiedFactor|CacheLock|CacheLockMaxAge|CacheLockPath|CacheMaxExpire|CacheMaxFileSize|CacheMinExpire|CacheMinFileSize|CacheNegotiatedDocs|CacheQuickHandler|CacheReadSize|CacheReadTime|CacheRoot|CacheSocache|CacheSocacheMaxSize|CacheSocacheMaxTime|CacheSocacheMinTime|CacheSocacheReadSize|CacheSocacheReadTime|CacheStaleOnError|CacheStoreExpired|CacheStoreNoStore|CacheStorePrivate|CGIDScriptTimeout|CGIMapExtension|CharsetDefault|CharsetOptions|CharsetSourceEnc|CheckCaseOnly|CheckSpelling|ChrootDir|ContentDigest|CookieDomain|CookieExpires|CookieName|CookieStyle|CookieTracking|CoreDumpDirectory|CustomLog|Dav|DavDepthInfinity|DavGenericLockDB|DavLockDB|DavMinTimeout|DBDExptime|DBDInitSQL|DBDKeep|DBDMax|DBDMin|DBDParams|DBDPersist|DBDPrepareSQL|DBDriver|DefaultIcon|DefaultLanguage|DefaultRuntimeDir|DefaultType|Define|DeflateBufferSize|DeflateCompressionLevel|DeflateFilterNote|DeflateInflateLimitRequestBody|DeflateInflateRatioBurst|DeflateInflateRatioLimit|DeflateMemLevel|DeflateWindowSize|Deny|DirectoryCheckHandler|DirectoryIndex|DirectoryIndexRedirect|DirectorySlash|DocumentRoot|DTracePrivileges|DumpIOInput|DumpIOOutput|EnableExceptionHook|EnableMMAP|EnableSendfile|Error|ErrorDocument|ErrorLog|ErrorLogFormat|Example|ExpiresActive|ExpiresByType|ExpiresDefault|ExtendedStatus|ExtFilterDefine|ExtFilterOptions|FallbackResource|FileETag|FilterChain|FilterDeclare|FilterProtocol|FilterProvider|FilterTrace|ForceLanguagePriority|ForceType|ForensicLog|GprofDir|GracefulShutdownTimeout|Group|Header|HeaderName|HeartbeatAddress|HeartbeatListen|HeartbeatMaxServers|HeartbeatStorage|HeartbeatStorage|HostnameLookups|IdentityCheck|IdentityCheckTimeout|ImapBase|ImapDefault|ImapMenu|Include|IncludeOptional|IndexHeadInsert|IndexIgnore|IndexIgnoreReset|IndexOptions|IndexOrderDefault|IndexStyleSheet|InputSed|ISAPIAppendLogToErrors|ISAPIAppendLogToQuery|ISAPICacheFile|ISAPIFakeAsync|ISAPILogNotSupported|ISAPIReadAheadBuffer|KeepAlive|KeepAliveTimeout|KeptBodySize|LanguagePriority|LDAPCacheEntries|LDAPCacheTTL|LDAPConnectionPoolTTL|LDAPConnectionTimeout|LDAPLibraryDebug|LDAPOpCacheEntries|LDAPOpCacheTTL|LDAPReferralHopLimit|LDAPReferrals|LDAPRetries|LDAPRetryDelay|LDAPSharedCacheFile|LDAPSharedCacheSize|LDAPTimeout|LDAPTrustedClientCert|LDAPTrustedGlobalCert|LDAPTrustedMode|LDAPVerifyServerCert|LimitInternalRecursion|LimitRequestBody|LimitRequestFields|LimitRequestFieldSize|LimitRequestLine|LimitXMLRequestBody|Listen|ListenBackLog|LoadFile|LoadModule|LogFormat|LogLevel|LogMessage|LuaAuthzProvider|LuaCodeCache|LuaHookAccessChecker|LuaHookAuthChecker|LuaHookCheckUserID|LuaHookFixups|LuaHookInsertFilter|LuaHookLog|LuaHookMapToStorage|LuaHookTranslateName|LuaHookTypeChecker|LuaInherit|LuaInputFilter|LuaMapHandler|LuaOutputFilter|LuaPackageCPath|LuaPackagePath|LuaQuickHandler|LuaRoot|LuaScope|MaxConnectionsPerChild|MaxKeepAliveRequests|MaxMemFree|MaxRangeOverlaps|MaxRangeReversals|MaxRanges|MaxRequestWorkers|MaxSpareServers|MaxSpareThreads|MaxThreads|MergeTrailers|MetaDir|MetaFiles|MetaSuffix|MimeMagicFile|MinSpareServers|MinSpareThreads|MMapFile|ModemStandard|ModMimeUsePathInfo|MultiviewsMatch|Mutex|NameVirtualHost|NoProxy|NWSSLTrustedCerts|NWSSLUpgradeable|Options|Order|OutputSed|PassEnv|PidFile|PrivilegesMode|Protocol|ProtocolEcho|ProxyAddHeaders|ProxyBadHeader|ProxyBlock|ProxyDomain|ProxyErrorOverride|ProxyExpressDBMFile|ProxyExpressDBMType|ProxyExpressEnable|ProxyFtpDirCharset|ProxyFtpEscapeWildcards|ProxyFtpListOnWildcard|ProxyHTMLBufSize|ProxyHTMLCharsetOut|ProxyHTMLDocType|ProxyHTMLEnable|ProxyHTMLEvents|ProxyHTMLExtended|ProxyHTMLFixups|ProxyHTMLInterp|ProxyHTMLLinks|ProxyHTMLMeta|ProxyHTMLStripComments|ProxyHTMLURLMap|ProxyIOBufferSize|ProxyMaxForwards|ProxyPass|ProxyPassInherit|ProxyPassInterpolateEnv|ProxyPassMatch|ProxyPassReverse|ProxyPassReverseCookieDomain|ProxyPassReverseCookiePath|ProxyPreserveHost|ProxyReceiveBufferSize|ProxyRemote|ProxyRemoteMatch|ProxyRequests|ProxySCGIInternalRedirect|ProxySCGISendfile|ProxySet|ProxySourceAddress|ProxyStatus|ProxyTimeout|ProxyVia|ReadmeName|ReceiveBufferSize|Redirect|RedirectMatch|RedirectPermanent|RedirectTemp|ReflectorHeader|RemoteIPHeader|RemoteIPInternalProxy|RemoteIPInternalProxyList|RemoteIPProxiesHeader|RemoteIPTrustedProxy|RemoteIPTrustedProxyList|RemoveCharset|RemoveEncoding|RemoveHandler|RemoveInputFilter|RemoveLanguage|RemoveOutputFilter|RemoveType|RequestHeader|RequestReadTimeout|Require|RewriteBase|RewriteCond|RewriteEngine|RewriteMap|RewriteOptions|RewriteRule|RLimitCPU|RLimitMEM|RLimitNPROC|Satisfy|ScoreBoardFile|Script|ScriptAlias|ScriptAliasMatch|ScriptInterpreterSource|ScriptLog|ScriptLogBuffer|ScriptLogLength|ScriptSock|SecureListen|SeeRequestTail|SendBufferSize|ServerAdmin|ServerAlias|ServerLimit|ServerName|ServerPath|ServerRoot|ServerSignature|ServerTokens|Session|SessionCookieName|SessionCookieName2|SessionCookieRemove|SessionCryptoCipher|SessionCryptoDriver|SessionCryptoPassphrase|SessionCryptoPassphraseFile|SessionDBDCookieName|SessionDBDCookieName2|SessionDBDCookieRemove|SessionDBDDeleteLabel|SessionDBDInsertLabel|SessionDBDPerUser|SessionDBDSelectLabel|SessionDBDUpdateLabel|SessionEnv|SessionExclude|SessionHeader|SessionInclude|SessionMaxAge|SetEnv|SetEnvIf|SetEnvIfExpr|SetEnvIfNoCase|SetHandler|SetInputFilter|SetOutputFilter|SSIEndTag|SSIErrorMsg|SSIETag|SSILastModified|SSILegacyExprParser|SSIStartTag|SSITimeFormat|SSIUndefinedEcho|SSLCACertificateFile|SSLCACertificatePath|SSLCADNRequestFile|SSLCADNRequestPath|SSLCARevocationCheck|SSLCARevocationFile|SSLCARevocationPath|SSLCertificateChainFile|SSLCertificateFile|SSLCertificateKeyFile|SSLCipherSuite|SSLCompression|SSLCryptoDevice|SSLEngine|SSLFIPS|SSLHonorCipherOrder|SSLInsecureRenegotiation|SSLOCSPDefaultResponder|SSLOCSPEnable|SSLOCSPOverrideResponder|SSLOCSPResponderTimeout|SSLOCSPResponseMaxAge|SSLOCSPResponseTimeSkew|SSLOCSPUseRequestNonce|SSLOpenSSLConfCmd|SSLOptions|SSLPassPhraseDialog|SSLProtocol|SSLProxyCACertificateFile|SSLProxyCACertificatePath|SSLProxyCARevocationCheck|SSLProxyCARevocationFile|SSLProxyCARevocationPath|SSLProxyCheckPeerCN|SSLProxyCheckPeerExpire|SSLProxyCheckPeerName|SSLProxyCipherSuite|SSLProxyEngine|SSLProxyMachineCertificateChainFile|SSLProxyMachineCertificateFile|SSLProxyMachineCertificatePath|SSLProxyProtocol|SSLProxyVerify|SSLProxyVerifyDepth|SSLRandomSeed|SSLRenegBufferSize|SSLRequire|SSLRequireSSL|SSLSessionCache|SSLSessionCacheTimeout|SSLSessionTicketKeyFile|SSLSRPUnknownUserSeed|SSLSRPVerifierFile|SSLStaplingCache|SSLStaplingErrorCacheTimeout|SSLStaplingFakeTryLater|SSLStaplingForceURL|SSLStaplingResponderTimeout|SSLStaplingResponseMaxAge|SSLStaplingResponseTimeSkew|SSLStaplingReturnResponderErrors|SSLStaplingStandardCacheTimeout|SSLStrictSNIVHostCheck|SSLUserName|SSLUseStapling|SSLVerifyClient|SSLVerifyDepth|StartServers|StartThreads|Substitute|Suexec|SuexecUserGroup|ThreadLimit|ThreadsPerChild|ThreadStackSize|TimeOut|TraceEnable|TransferLog|TypesConfig|UnDefine|UndefMacro|UnsetEnv|Use|UseCanonicalName|UseCanonicalPhysicalPort|User|UserDir|VHostCGIMode|VHostCGIPrivs|VHostGroup|VHostPrivs|VHostSecure|VHostUser|VirtualDocumentRoot|VirtualDocumentRootIP|VirtualScriptAlias|VirtualScriptAliasIP|WatchdogInterval|XBitHack|xml2EncAlias|xml2EncDefault|xml2StartParse)\b/im,lookbehind:!0,alias:"property"},"directive-block":{pattern:/<\/?\b(AuthnProviderAlias|AuthzProviderAlias|Directory|DirectoryMatch|Else|ElseIf|Files|FilesMatch|If|IfDefine|IfModule|IfVersion|Limit|LimitExcept|Location|LocationMatch|Macro|Proxy|RequireAll|RequireAny|RequireNone|VirtualHost)\b *.*>/i,inside:{"directive-block":{pattern:/^<\/?\w+/,inside:{punctuation:/^<\/?/},alias:"tag"},"directive-block-parameter":{pattern:/.*[^>]/,inside:{punctuation:/:/,string:{pattern:/("|').*\1/,inside:{variable:/(\$|%)\{?(\w\.?(\+|\-|:)?)+\}?/}}},alias:"attr-value"},punctuation:/>/},alias:"tag"},"directive-flags":{pattern:/\[(\w,?)+\]/,alias:"keyword"},string:{pattern:/("|').*\1/,inside:{variable:/(\$|%)\{?(\w\.?(\+|\-|:)?)+\}?/}},variable:/(\$|%)\{?(\w\.?(\+|\-|:)?)+\}?/,regex:/\^?.*\$|\^.*\$?/}; 8 | !function(e){var t={variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee]-?\d+)?)\b/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\([^)]+\)|`[^`]+`/,inside:{variable:/^\$\(|^`|\)$|`$/}},/\$(?:[a-z0-9_#\?\*!@]+|\{[^}]+\})/i]};e.languages.bash={shebang:{pattern:/^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/,alias:"important"},comment:{pattern:/(^|[^"{\\])#.*/,lookbehind:!0},string:[{pattern:/((?:^|[^<])<<\s*)(?:"|')?(\w+?)(?:"|')?\s*\r?\n(?:[\s\S])*?\r?\n\2/g,lookbehind:!0,greedy:!0,inside:t},{pattern:/(["'])(?:\\\\|\\?[^\\])*?\1/g,greedy:!0,inside:t}],variable:t.variable,"function":{pattern:/(^|\s|;|\||&)(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|npm|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|\s|;|\||&)/,lookbehind:!0},keyword:{pattern:/(^|\s|;|\||&)(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|\s|;|\||&)/,lookbehind:!0},"boolean":{pattern:/(^|\s|;|\||&)(?:true|false)(?=$|\s|;|\||&)/,lookbehind:!0},operator:/&&?|\|\|?|==?|!=?|<<>|<=?|>=?|=~/,punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];]/};var a=t.variable[1].inside;a["function"]=e.languages.bash["function"],a.keyword=e.languages.bash.keyword,a.boolean=e.languages.bash.boolean,a.operator=e.languages.bash.operator,a.punctuation=e.languages.bash.punctuation}(Prism); 9 | Prism.languages.c=Prism.languages.extend("clike",{keyword:/\b(asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/\-[>-]?|\+\+?|!=?|<>?=?|==?|&&?|\|?\||[~^%?*\/]/,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+([^\r\n\\]|\\.|\\(?:\r\n?|\n))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/,lookbehind:!0},directive:{pattern:/(#\s*)\b(define|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,lookbehind:!0,alias:"keyword"}}},constant:/\b(__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|stdin|stdout|stderr)\b/}),delete Prism.languages.c["class-name"],delete Prism.languages.c["boolean"]; 10 | Prism.languages.csharp=Prism.languages.extend("clike",{keyword:/\b(abstract|as|async|await|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|do|double|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|virtual|void|volatile|while|add|alias|ascending|async|await|descending|dynamic|from|get|global|group|into|join|let|orderby|partial|remove|select|set|value|var|where|yield)\b/,string:[{pattern:/@("|')(\1\1|\\\1|\\?(?!\1)[\s\S])*\1/,greedy:!0},{pattern:/("|')(\\?.)*?\1/,greedy:!0}],number:/\b-?(0x[\da-f]+|\d*\.?\d+f?)\b/i}),Prism.languages.insertBefore("csharp","keyword",{"generic-method":{pattern:/[a-z0-9_]+\s*<[^>\r\n]+?>\s*(?=\()/i,alias:"function",inside:{keyword:Prism.languages.csharp.keyword,punctuation:/[<>(),.:]/}},preprocessor:{pattern:/(^\s*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(\s*#)\b(define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}}); 11 | Prism.languages.cpp=Prism.languages.extend("c",{keyword:/\b(alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,"boolean":/\b(true|false)\b/,operator:/[-+]{1,2}|!=?|<{1,2}=?|>{1,2}=?|\->|:{1,2}|={1,2}|\^|~|%|&{1,2}|\|?\||\?|\*|\/|\b(and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/}),Prism.languages.insertBefore("cpp","keyword",{"class-name":{pattern:/(class\s+)[a-z0-9_]+/i,lookbehind:!0}}); 12 | !function(e){e.languages.ruby=e.languages.extend("clike",{comment:[/#(?!\{[^\r\n]*?\}).*/,/^=begin(?:\r?\n|\r)(?:.*(?:\r?\n|\r))*?=end/m],keyword:/\b(alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|false|for|if|in|module|new|next|nil|not|or|raise|redo|require|rescue|retry|return|self|super|then|throw|true|undef|unless|until|when|while|yield)\b/});var n={pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"tag"},rest:e.util.clone(e.languages.ruby)}};e.languages.insertBefore("ruby","keyword",{regex:[{pattern:/%r([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\((?:[^()\\]|\\[\s\S])*\)[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\[(?:[^\[\]\\]|\\[\s\S])*\][gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r<(?:[^<>\\]|\\[\s\S])*>[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}],variable:/[@$]+[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/,symbol:/:[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/}),e.languages.insertBefore("ruby","number",{builtin:/\b(Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|File|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,constant:/\b[A-Z][a-zA-Z_0-9]*(?:[?!]|\b)/}),e.languages.ruby.string=[{pattern:/%[qQiIwWxs]?([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\[(?:[^\[\]\\]|\\[\s\S])*\]/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0,inside:{interpolation:n}},{pattern:/("|')(#\{[^}]+\}|\\(?:\r?\n|\r)|\\?.)*?\1/,greedy:!0,inside:{interpolation:n}}]}(Prism); 13 | Prism.languages.css.selector={pattern:/[^\{\}\s][^\{\}]*(?=\s*\{)/,inside:{"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+(?:\(.*\))?/,"class":/\.[-:\.\w]+/,id:/#[-:\.\w]+/,attribute:/\[[^\]]+\]/}},Prism.languages.insertBefore("css","function",{hexcode:/#[\da-f]{3,8}/i,entity:/\\[\da-f]{1,8}/i,number:/[\d%\.]+/}); 14 | Prism.languages.docker={keyword:{pattern:/(^\s*)(?:ONBUILD|FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|COPY|VOLUME|USER|WORKDIR|CMD|LABEL|ENTRYPOINT)(?=\s)/mi,lookbehind:true},string:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*?\1/,comment:/#.*/,punctuation:/---|\.\.\.|[:[\]{}\-,|>?]/};Prism.languages.dockerfile=Prism.languages.docker; 15 | Prism.languages.git={comment:/^#.*/m,deleted:/^[-–].*/m,inserted:/^\+.*/m,string:/("|')(\\?.)*?\1/m,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s(--|-)\w+/m}},coord:/^@@.*@@$/m,commit_sha1:/^commit \w{40}$/m}; 16 | Prism.languages.go=Prism.languages.extend("clike",{keyword:/\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,builtin:/\b(bool|byte|complex(64|128)|error|float(32|64)|rune|string|u?int(8|16|32|64|)|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(ln)?|real|recover)\b/,"boolean":/\b(_|iota|nil|true|false)\b/,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,number:/\b(-?(0x[a-f\d]+|(\d+\.?\d*|\.\d+)(e[-+]?\d+)?)i?)\b/i,string:{pattern:/("|'|`)(\\?.|\r|\n)*?\1/,greedy:!0}}),delete Prism.languages.go["class-name"]; 17 | Prism.languages.graphql={comment:/#.*/,string:{pattern:/"(?:\\.|[^\\"])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b/,"boolean":/\b(?:true|false)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":/[a-z_]\w*(?=\s*:)/i,keyword:[{pattern:/(fragment\s+(?!on)[a-z_]\w*\s+|\.\.\.\s*)on\b/,lookbehind:!0},/\b(?:query|fragment|mutation)\b/],operator:/!|=|\.{3}/,punctuation:/[!(){}\[\]:=,]/}; 18 | Prism.languages.groovy=Prism.languages.extend("clike",{keyword:/\b(as|def|in|abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|trait|transient|try|void|volatile|while)\b/,string:[{pattern:/("""|''')[\s\S]*?\1|(\$\/)(\$\/\$|[\s\S])*?\/\$/,greedy:!0},{pattern:/("|'|\/)(?:\\?.)*?\1/,greedy:!0}],number:/\b(?:0b[01_]+|0x[\da-f_]+(?:\.[\da-f_p\-]+)?|[\d_]+(?:\.[\d_]+)?(?:e[+-]?[\d]+)?)[glidf]?\b/i,operator:{pattern:/(^|[^.])(~|==?~?|\?[.:]?|\*(?:[.=]|\*=?)?|\.[@&]|\.\.<|\.{1,2}(?!\.)|-[-=>]?|\+[+=]?|!=?|<(?:<=?|=>?)?|>(?:>>?=?|=)?|&[&=]?|\|[|=]?|\/=?|\^=?|%=?)/,lookbehind:!0},punctuation:/\.+|[{}[\];(),:$]/}),Prism.languages.insertBefore("groovy","string",{shebang:{pattern:/#!.+/,alias:"comment"}}),Prism.languages.insertBefore("groovy","punctuation",{"spock-block":/\b(setup|given|when|then|and|cleanup|expect|where):/}),Prism.languages.insertBefore("groovy","function",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0}}),Prism.hooks.add("wrap",function(e){if("groovy"===e.language&&"string"===e.type){var t=e.content[0];if("'"!=t){var n=/([^\\])(\$(\{.*?\}|[\w\.]+))/;"$"===t&&(n=/([^\$])(\$(\{.*?\}|[\w\.]+))/),e.content=e.content.replace(/</g,"<").replace(/&/g,"&"),e.content=Prism.highlight(e.content,{expression:{pattern:n,lookbehind:!0,inside:Prism.languages.groovy}}),e.classes.push("/"===t?"regex":"gstring")}}}); 19 | Prism.languages.http={"request-line":{pattern:/^(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b\shttps?:\/\/\S+\sHTTP\/[0-9.]+/m,inside:{property:/^(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b/,"attr-name":/:\w+/}},"response-status":{pattern:/^HTTP\/1.[01] \d+.*/m,inside:{property:{pattern:/(^HTTP\/1.[01] )\d+.*/i,lookbehind:!0}}},"header-name":{pattern:/^[\w-]+:(?=.)/m,alias:"keyword"}};var httpLanguages={"application/json":Prism.languages.javascript,"application/xml":Prism.languages.markup,"text/xml":Prism.languages.markup,"text/html":Prism.languages.markup};for(var contentType in httpLanguages)if(httpLanguages[contentType]){var options={};options[contentType]={pattern:new RegExp("(content-type:\\s*"+contentType+"[\\w\\W]*?)(?:\\r?\\n|\\r){2}[\\w\\W]*","i"),lookbehind:!0,inside:{rest:httpLanguages[contentType]}},Prism.languages.insertBefore("http","header-name",options)}; 20 | Prism.languages.java=Prism.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,lookbehind:!0}}),Prism.languages.insertBefore("java","function",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0}}); 21 | Prism.languages.json={property:/"(?:\\.|[^\\"])*"(?=\s*:)/gi,string:/"(?!:)(?:\\.|[^\\"])*"(?!:)/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?)\b/g,punctuation:/[{}[\]);,]/g,operator:/:/g,"boolean":/\b(true|false)\b/gi,"null":/\bnull\b/gi},Prism.languages.jsonp=Prism.languages.json; 22 | !function(n){n.languages.kotlin=n.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|else|enum|final|finally|for|fun|get|if|import|in|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|out|override|package|private|protected|public|reified|return|sealed|set|super|tailrec|this|throw|to|try|val|var|when|where|while)\b/,lookbehind:!0},"function":[/\w+(?=\s*\()/,{pattern:/(\.)\w+(?=\s*\{)/,lookbehind:!0}],number:/\b(?:0[bx][\da-fA-F]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete n.languages.kotlin["class-name"],n.languages.insertBefore("kotlin","string",{"raw-string":{pattern:/(["'])\1\1[\s\S]*?\1{3}/,alias:"string"}}),n.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),n.languages.insertBefore("kotlin","function",{label:{pattern:/\w+@|@\w+/,alias:"symbol"}});var e=[{pattern:/\$\{[^}]+\}/,inside:{delimiter:{pattern:/^\$\{|\}$/,alias:"variable"},rest:n.util.clone(n.languages.kotlin)}},{pattern:/\$\w+/,alias:"variable"}];n.languages.kotlin.string.inside=n.languages.kotlin["raw-string"].inside={interpolation:e}}(Prism); 23 | !function(a){var e=/\\([^a-z()[\]]|[a-z\*]+)/i,n={"equation-command":{pattern:e,alias:"regex"}};a.languages.latex={comment:/%.*/m,cdata:{pattern:/(\\begin\{((?:verbatim|lstlisting)\*?)\})([\s\S]*?)(?=\\end\{\2\})/,lookbehind:!0},equation:[{pattern:/\$(?:\\?[\s\S])*?\$|\\\((?:\\?[\s\S])*?\\\)|\\\[(?:\\?[\s\S])*?\\\]/,inside:n,alias:"string"},{pattern:/(\\begin\{((?:equation|math|eqnarray|align|multline|gather)\*?)\})([\s\S]*?)(?=\\end\{\2\})/,lookbehind:!0,inside:n,alias:"string"}],keyword:{pattern:/(\\(?:begin|end|ref|cite|label|usepackage|documentclass)(?:\[[^\]]+\])?\{)[^}]+(?=\})/,lookbehind:!0},url:{pattern:/(\\url\{)[^}]+(?=\})/,lookbehind:!0},headline:{pattern:/(\\(?:part|chapter|section|subsection|frametitle|subsubsection|paragraph|subparagraph|subsubparagraph|subsubsubparagraph)\*?(?:\[[^\]]+\])?\{)[^}]+(?=\}(?:\[[^\]]+\])?)/,lookbehind:!0,alias:"class-name"},"function":{pattern:e,alias:"selector"},punctuation:/[[\]{}&]/}}(Prism); 24 | Prism.languages.less=Prism.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-]+?(?:\([^{}]+\)|[^(){};])*?(?=\s*\{)/i,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\([^{}]*\)|[^{};@])*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/i,punctuation:/[{}();:,]/,operator:/[+\-*\/]/}),Prism.languages.insertBefore("less","punctuation",{"function":Prism.languages.less.function}),Prism.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-]+.*?(?=[(;])/,lookbehind:!0,alias:"function"}}); 25 | Prism.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|.)*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},builtin:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,symbol:{pattern:/^[^:=\r\n]+(?=\s*:(?!=))/m,inside:{variable:/\$+(?:[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:[/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,{pattern:/(\()(?:addsuffix|abspath|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:s|list)?)(?=[ \t])/,lookbehind:!0}],operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/}; 26 | Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},code:[{pattern:/^(?: {4}|\t).+/m,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*(?:\r?\n|\r)(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*$|__$/}},italic:{pattern:/(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold); 27 | Prism.languages.nginx=Prism.languages.extend("clike",{comment:{pattern:/(^|[^"{\\])#.*/,lookbehind:!0},keyword:/\b(?:CONTENT_|DOCUMENT_|GATEWAY_|HTTP_|HTTPS|if_not_empty|PATH_|QUERY_|REDIRECT_|REMOTE_|REQUEST_|SCGI|SCRIPT_|SERVER_|http|server|events|location|include|accept_mutex|accept_mutex_delay|access_log|add_after_body|add_before_body|add_header|addition_types|aio|alias|allow|ancient_browser|ancient_browser_value|auth|auth_basic|auth_basic_user_file|auth_http|auth_http_header|auth_http_timeout|autoindex|autoindex_exact_size|autoindex_localtime|break|charset|charset_map|charset_types|chunked_transfer_encoding|client_body_buffer_size|client_body_in_file_only|client_body_in_single_buffer|client_body_temp_path|client_body_timeout|client_header_buffer_size|client_header_timeout|client_max_body_size|connection_pool_size|create_full_put_path|daemon|dav_access|dav_methods|debug_connection|debug_points|default_type|deny|devpoll_changes|devpoll_events|directio|directio_alignment|disable_symlinks|empty_gif|env|epoll_events|error_log|error_page|expires|fastcgi_buffer_size|fastcgi_buffers|fastcgi_busy_buffers_size|fastcgi_cache|fastcgi_cache_bypass|fastcgi_cache_key|fastcgi_cache_lock|fastcgi_cache_lock_timeout|fastcgi_cache_methods|fastcgi_cache_min_uses|fastcgi_cache_path|fastcgi_cache_purge|fastcgi_cache_use_stale|fastcgi_cache_valid|fastcgi_connect_timeout|fastcgi_hide_header|fastcgi_ignore_client_abort|fastcgi_ignore_headers|fastcgi_index|fastcgi_intercept_errors|fastcgi_keep_conn|fastcgi_max_temp_file_size|fastcgi_next_upstream|fastcgi_no_cache|fastcgi_param|fastcgi_pass|fastcgi_pass_header|fastcgi_read_timeout|fastcgi_redirect_errors|fastcgi_send_timeout|fastcgi_split_path_info|fastcgi_store|fastcgi_store_access|fastcgi_temp_file_write_size|fastcgi_temp_path|flv|geo|geoip_city|geoip_country|google_perftools_profiles|gzip|gzip_buffers|gzip_comp_level|gzip_disable|gzip_http_version|gzip_min_length|gzip_proxied|gzip_static|gzip_types|gzip_vary|if|if_modified_since|ignore_invalid_headers|image_filter|image_filter_buffer|image_filter_jpeg_quality|image_filter_sharpen|image_filter_transparency|imap_capabilities|imap_client_buffer|include|index|internal|ip_hash|keepalive|keepalive_disable|keepalive_requests|keepalive_timeout|kqueue_changes|kqueue_events|large_client_header_buffers|limit_conn|limit_conn_log_level|limit_conn_zone|limit_except|limit_rate|limit_rate_after|limit_req|limit_req_log_level|limit_req_zone|limit_zone|lingering_close|lingering_time|lingering_timeout|listen|location|lock_file|log_format|log_format_combined|log_not_found|log_subrequest|map|map_hash_bucket_size|map_hash_max_size|master_process|max_ranges|memcached_buffer_size|memcached_connect_timeout|memcached_next_upstream|memcached_pass|memcached_read_timeout|memcached_send_timeout|merge_slashes|min_delete_depth|modern_browser|modern_browser_value|mp4|mp4_buffer_size|mp4_max_buffer_size|msie_padding|msie_refresh|multi_accept|open_file_cache|open_file_cache_errors|open_file_cache_min_uses|open_file_cache_valid|open_log_file_cache|optimize_server_names|override_charset|pcre_jit|perl|perl_modules|perl_require|perl_set|pid|pop3_auth|pop3_capabilities|port_in_redirect|post_action|postpone_output|protocol|proxy|proxy_buffer|proxy_buffer_size|proxy_buffering|proxy_buffers|proxy_busy_buffers_size|proxy_cache|proxy_cache_bypass|proxy_cache_key|proxy_cache_lock|proxy_cache_lock_timeout|proxy_cache_methods|proxy_cache_min_uses|proxy_cache_path|proxy_cache_use_stale|proxy_cache_valid|proxy_connect_timeout|proxy_cookie_domain|proxy_cookie_path|proxy_headers_hash_bucket_size|proxy_headers_hash_max_size|proxy_hide_header|proxy_http_version|proxy_ignore_client_abort|proxy_ignore_headers|proxy_intercept_errors|proxy_max_temp_file_size|proxy_method|proxy_next_upstream|proxy_no_cache|proxy_pass|proxy_pass_error_message|proxy_pass_header|proxy_pass_request_body|proxy_pass_request_headers|proxy_read_timeout|proxy_redirect|proxy_redirect_errors|proxy_send_lowat|proxy_send_timeout|proxy_set_body|proxy_set_header|proxy_ssl_session_reuse|proxy_store|proxy_store_access|proxy_temp_file_write_size|proxy_temp_path|proxy_timeout|proxy_upstream_fail_timeout|proxy_upstream_max_fails|random_index|read_ahead|real_ip_header|recursive_error_pages|request_pool_size|reset_timedout_connection|resolver|resolver_timeout|return|rewrite|root|rtsig_overflow_events|rtsig_overflow_test|rtsig_overflow_threshold|rtsig_signo|satisfy|satisfy_any|secure_link_secret|send_lowat|send_timeout|sendfile|sendfile_max_chunk|server|server_name|server_name_in_redirect|server_names_hash_bucket_size|server_names_hash_max_size|server_tokens|set|set_real_ip_from|smtp_auth|smtp_capabilities|so_keepalive|source_charset|split_clients|ssi|ssi_silent_errors|ssi_types|ssi_value_length|ssl|ssl_certificate|ssl_certificate_key|ssl_ciphers|ssl_client_certificate|ssl_crl|ssl_dhparam|ssl_engine|ssl_prefer_server_ciphers|ssl_protocols|ssl_session_cache|ssl_session_timeout|ssl_verify_client|ssl_verify_depth|starttls|stub_status|sub_filter|sub_filter_once|sub_filter_types|tcp_nodelay|tcp_nopush|timeout|timer_resolution|try_files|types|types_hash_bucket_size|types_hash_max_size|underscores_in_headers|uninitialized_variable_warn|upstream|use|user|userid|userid_domain|userid_expires|userid_name|userid_p3p|userid_path|userid_service|valid_referers|variables_hash_bucket_size|variables_hash_max_size|worker_connections|worker_cpu_affinity|worker_priority|worker_processes|worker_rlimit_core|worker_rlimit_nofile|worker_rlimit_sigpending|working_directory|xclient|xml_entities|xslt_entities|xslt_stylesheet|xslt_types)\b/i}),Prism.languages.insertBefore("nginx","keyword",{variable:/\$[a-z_]+/i}); 28 | Prism.languages.objectivec=Prism.languages.extend("c",{keyword:/\b(asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while|in|self|super)\b|(@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,string:/("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|@"(\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}); 29 | Prism.languages.php=Prism.languages.extend("clike",{keyword:/\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i,constant:/\b[A-Z0-9_]{2,}\b/,comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0}}),Prism.languages.insertBefore("php","class-name",{"shell-comment":{pattern:/(^|[^\\])#.*/,lookbehind:!0,alias:"comment"}}),Prism.languages.insertBefore("php","keyword",{delimiter:{pattern:/\?>|<\?(?:php|=)?/i,alias:"important"},variable:/\$\w+\b/i,"package":{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),Prism.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}}),Prism.languages.markup&&(Prism.hooks.add("before-highlight",function(e){"php"===e.language&&/(?:<\?php|<\?)/gi.test(e.code)&&(e.tokenStack=[],e.backupCode=e.code,e.code=e.code.replace(/(?:<\?php|<\?)[\s\S]*?(?:\?>|$)/gi,function(a){for(var n=e.tokenStack.length;-1!==e.backupCode.indexOf("___PHP"+n+"___");)++n;return e.tokenStack[n]=a,"___PHP"+n+"___"}),e.grammar=Prism.languages.markup)}),Prism.hooks.add("before-insert",function(e){"php"===e.language&&e.backupCode&&(e.code=e.backupCode,delete e.backupCode)}),Prism.hooks.add("after-highlight",function(e){if("php"===e.language&&e.tokenStack){e.grammar=Prism.languages.php;for(var a=0,n=Object.keys(e.tokenStack);a'+Prism.highlight(r,e.grammar,"php").replace(/\$/g,"$$$$")+"")}e.element.innerHTML=e.highlightedCode}})); 30 | Prism.languages.properties={comment:/^[ \t]*[#!].*$/m,"attr-value":{pattern:/(^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+?(?: *[=:] *| ))(?:\\(?:\r\n|[\s\S])|.)+/m,lookbehind:!0},"attr-name":/^[ \t]*(?:\\(?:\r\n|[\s\S])|[^\\\s:=])+?(?= *[ =:]| )/m,punctuation:/[=:]/}; 31 | Prism.languages.protobuf=Prism.languages.extend("clike",{keyword:/\b(package|import|message|enum)\b/,builtin:/\b(required|repeated|optional|reserved)\b/,primitive:{pattern:/\b(double|float|int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes)\b/,alias:"symbol"}}); 32 | Prism.languages.python={"triple-quoted-string":{pattern:/"""[\s\S]+?"""|'''[\s\S]+?'''/,alias:"string"},comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:{pattern:/("|')(?:\\\\|\\?[^\\\r\n])*?\1/,greedy:!0},"function":{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_][a-zA-Z0-9_]*(?=\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)[a-z0-9_]+/i,lookbehind:!0},keyword:/\b(?:as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|pass|print|raise|return|try|while|with|yield)\b/,"boolean":/\b(?:True|False)\b/,number:/\b-?(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,punctuation:/[{}[\];(),.:]/}; 33 | !function(a){var e=a.util.clone(a.languages.javascript);a.languages.jsx=a.languages.extend("markup",e),a.languages.jsx.tag.pattern=/<\/?[\w\.:-]+\s*(?:\s+(?:[\w\.:-]+(?:=(?:("|')(\\?[\s\S])*?\1|[^\s'">=]+|(\{[\s\S]*?\})))?|\{\.{3}\w+\}))*\s*\/?>/i,a.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:('|")[\s\S]*?(\1)|[^\s>]+)/i,a.languages.insertBefore("inside","attr-name",{spread:{pattern:/\{\.{3}\w+\}/,inside:{punctuation:/\{|\}|\./,"attr-value":/\w+/}}},a.languages.jsx.tag);var s=a.util.clone(a.languages.jsx);delete s.punctuation,s=a.languages.insertBefore("jsx","operator",{punctuation:/=(?={)|[{}[\];(),.:]/},{jsx:s}),a.languages.insertBefore("inside","attr-value",{script:{pattern:/=(\{(?:\{[^}]*\}|[^}])+\})/i,inside:s,alias:"language-javascript"}},a.languages.jsx.tag)}(Prism); 34 | Prism.languages.rust={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:[{pattern:/b?r(#*)"(?:\\?.)*?"\1/,greedy:!0},{pattern:/b?("|')(?:\\?.)*?\1/,greedy:!0}],keyword:/\b(?:abstract|alignof|as|be|box|break|const|continue|crate|do|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/,attribute:{pattern:/#!?\[.+?\]/,greedy:!0,alias:"attr-name"},"function":[/[a-z0-9_]+(?=\s*\()/i,/[a-z0-9_]+!(?=\s*\(|\[)/i],"macro-rules":{pattern:/[a-z0-9_]+!/i,alias:"function"},number:/\b-?(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(\d(_?\d)*)?\.?\d(_?\d)*([Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64)?|f32|f64))?\b/,"closure-params":{pattern:/\|[^|]*\|(?=\s*[{-])/,inside:{punctuation:/[\|:,]/,operator:/[&*]/}},punctuation:/[{}[\];(),:]|\.+|->/,operator:/[-+*\/%!^=]=?|@|&[&=]?|\|[|=]?|<>?=?/}; 35 | !function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t]+.+)*/m,lookbehind:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,inside:{atrule:/(?:@[\w-]+|[+=])/m}}}),delete e.languages.sass.atrule;var a=/((\$[-_\w]+)|(#\{\$[-_\w]+\}))/i,t=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|or|not)\b/,{pattern:/(\s+)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,inside:{punctuation:/:/,variable:a,operator:t}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s]+.*)/m,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:a,operator:t,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,delete e.languages.sass.selector,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/([ \t]*)\S(?:,?[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,?[^,\r\n]+)*)*/,lookbehind:!0}})}(Prism); 36 | Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)*url(?=\()/i,selector:{pattern:/(?=\S)[^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-_\w]+/,variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}}}),Prism.languages.insertBefore("scss","atrule",{keyword:[/@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,{pattern:/( +)(?:from|through)(?= )/,lookbehind:!0}]}),Prism.languages.scss.property={pattern:/(?:[\w-]|\$[-_\w]+|#\{\$[-_\w]+\})+(?=\s*:)/i,inside:{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}},Prism.languages.insertBefore("scss","important",{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-_\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},"boolean":/\b(?:true|false)\b/,"null":/\bnull\b/,operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,lookbehind:!0}}),Prism.languages.scss.atrule.inside.rest=Prism.util.clone(Prism.languages.scss); 37 | Prism.languages.swift=Prism.languages.extend("clike",{string:{pattern:/("|')(\\(?:\((?:[^()]|\([^)]+\))+\)|\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:{pattern:/\\\((?:[^()]|\([^)]+\))+\)/,inside:{delimiter:{pattern:/^\\\(|\)$/,alias:"variable"}}}}},keyword:/\b(as|associativity|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic(?:Type)?|else|enum|extension|fallthrough|final|for|func|get|guard|if|import|in|infix|init|inout|internal|is|lazy|left|let|mutating|new|none|nonmutating|operator|optional|override|postfix|precedence|prefix|private|Protocol|public|repeat|required|rethrows|return|right|safe|self|Self|set|static|struct|subscript|super|switch|throws?|try|Type|typealias|unowned|unsafe|var|weak|where|while|willSet|__(?:COLUMN__|FILE__|FUNCTION__|LINE__))\b/,number:/\b([\d_]+(\.[\de_]+)?|0x[a-f0-9_]+(\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,constant:/\b(nil|[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,atrule:/@\b(IB(?:Outlet|Designable|Action|Inspectable)|class_protocol|exported|noreturn|NS(?:Copying|Managed)|objc|UIApplicationMain|auto_closure)\b/,builtin:/\b([A-Z]\S+|abs|advance|alignof(?:Value)?|assert|contains|count(?:Elements)?|debugPrint(?:ln)?|distance|drop(?:First|Last)|dump|enumerate|equal|filter|find|first|getVaList|indices|isEmpty|join|last|lexicographicalCompare|map|max(?:Element)?|min(?:Element)?|numericCast|overlaps|partition|print(?:ln)?|reduce|reflect|reverse|sizeof(?:Value)?|sort(?:ed)?|split|startsWith|stride(?:of(?:Value)?)?|suffix|swap|toDebugString|toString|transcode|underestimateCount|unsafeBitCast|with(?:ExtendedLifetime|Unsafe(?:MutablePointers?|Pointers?)|VaList))\b/}),Prism.languages.swift.string.inside.interpolation.inside.rest=Prism.util.clone(Prism.languages.swift); 38 | Prism.languages.vim={string:/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\r\n]|'')*'/,comment:/".*/,"function":/\w+(?=\()/,keyword:/\b(?:ab|abbreviate|abc|abclear|abo|aboveleft|al|all|arga|argadd|argd|argdelete|argdo|arge|argedit|argg|argglobal|argl|arglocal|ar|args|argu|argument|as|ascii|bad|badd|ba|ball|bd|bdelete|be|bel|belowright|bf|bfirst|bl|blast|bm|bmodified|bn|bnext|bN|bNext|bo|botright|bp|bprevious|brea|break|breaka|breakadd|breakd|breakdel|breakl|breaklist|br|brewind|bro|browse|bufdo|b|buffer|buffers|bun|bunload|bw|bwipeout|ca|cabbrev|cabc|cabclear|caddb|caddbuffer|cad|caddexpr|caddf|caddfile|cal|call|cat|catch|cb|cbuffer|cc|ccl|cclose|cd|ce|center|cex|cexpr|cf|cfile|cfir|cfirst|cgetb|cgetbuffer|cgete|cgetexpr|cg|cgetfile|c|change|changes|chd|chdir|che|checkpath|checkt|checktime|cla|clast|cl|clist|clo|close|cmapc|cmapclear|cnew|cnewer|cn|cnext|cN|cNext|cnf|cnfile|cNfcNfile|cnorea|cnoreabbrev|col|colder|colo|colorscheme|comc|comclear|comp|compiler|conf|confirm|con|continue|cope|copen|co|copy|cpf|cpfile|cp|cprevious|cq|cquit|cr|crewind|cuna|cunabbrev|cu|cunmap|cw|cwindow|debugg|debuggreedy|delc|delcommand|d|delete|delf|delfunction|delm|delmarks|diffg|diffget|diffoff|diffpatch|diffpu|diffput|diffsplit|diffthis|diffu|diffupdate|dig|digraphs|di|display|dj|djump|dl|dlist|dr|drop|ds|dsearch|dsp|dsplit|earlier|echoe|echoerr|echom|echomsg|echon|e|edit|el|else|elsei|elseif|em|emenu|endfo|endfor|endf|endfunction|endfun|en|endif|endt|endtry|endw|endwhile|ene|enew|ex|exi|exit|exu|exusage|f|file|files|filetype|fina|finally|fin|find|fini|finish|fir|first|fix|fixdel|fo|fold|foldc|foldclose|folddoc|folddoclosed|foldd|folddoopen|foldo|foldopen|for|fu|fun|function|go|goto|gr|grep|grepa|grepadd|ha|hardcopy|h|help|helpf|helpfind|helpg|helpgrep|helpt|helptags|hid|hide|his|history|ia|iabbrev|iabc|iabclear|if|ij|ijump|il|ilist|imapc|imapclear|in|inorea|inoreabbrev|isearch|isp|isplit|iuna|iunabbrev|iu|iunmap|j|join|ju|jumps|k|keepalt|keepj|keepjumps|kee|keepmarks|laddb|laddbuffer|lad|laddexpr|laddf|laddfile|lan|language|la|last|later|lb|lbuffer|lc|lcd|lch|lchdir|lcl|lclose|let|left|lefta|leftabove|lex|lexpr|lf|lfile|lfir|lfirst|lgetb|lgetbuffer|lgete|lgetexpr|lg|lgetfile|lgr|lgrep|lgrepa|lgrepadd|lh|lhelpgrep|l|list|ll|lla|llast|lli|llist|lmak|lmake|lm|lmap|lmapc|lmapclear|lnew|lnewer|lne|lnext|lN|lNext|lnf|lnfile|lNf|lNfile|ln|lnoremap|lo|loadview|loc|lockmarks|lockv|lockvar|lol|lolder|lop|lopen|lpf|lpfile|lp|lprevious|lr|lrewind|ls|lt|ltag|lu|lunmap|lv|lvimgrep|lvimgrepa|lvimgrepadd|lw|lwindow|mak|make|ma|mark|marks|mat|match|menut|menutranslate|mk|mkexrc|mks|mksession|mksp|mkspell|mkvie|mkview|mkv|mkvimrc|mod|mode|m|move|mzf|mzfile|mz|mzscheme|nbkey|new|n|next|N|Next|nmapc|nmapclear|noh|nohlsearch|norea|noreabbrev|nu|number|nun|nunmap|omapc|omapclear|on|only|o|open|opt|options|ou|ounmap|pc|pclose|ped|pedit|pe|perl|perld|perldo|po|pop|popu|popu|popup|pp|ppop|pre|preserve|prev|previous|p|print|P|Print|profd|profdel|prof|profile|promptf|promptfind|promptr|promptrepl|ps|psearch|pta|ptag|ptf|ptfirst|ptj|ptjump|ptl|ptlast|ptn|ptnext|ptN|ptNext|ptp|ptprevious|ptr|ptrewind|pts|ptselect|pu|put|pw|pwd|pyf|pyfile|py|python|qa|qall|q|quit|quita|quitall|r|read|rec|recover|redi|redir|red|redo|redr|redraw|redraws|redrawstatus|reg|registers|res|resize|ret|retab|retu|return|rew|rewind|ri|right|rightb|rightbelow|rub|ruby|rubyd|rubydo|rubyf|rubyfile|ru|runtime|rv|rviminfo|sal|sall|san|sandbox|sa|sargument|sav|saveas|sba|sball|sbf|sbfirst|sbl|sblast|sbm|sbmodified|sbn|sbnext|sbN|sbNext|sbp|sbprevious|sbr|sbrewind|sb|sbuffer|scripte|scriptencoding|scrip|scriptnames|se|set|setf|setfiletype|setg|setglobal|setl|setlocal|sf|sfind|sfir|sfirst|sh|shell|sign|sil|silent|sim|simalt|sla|slast|sl|sleep|sm|smagic|sm|smap|smapc|smapclear|sme|smenu|sn|snext|sN|sNext|sni|sniff|sno|snomagic|snor|snoremap|snoreme|snoremenu|sor|sort|so|source|spelld|spelldump|spe|spellgood|spelli|spellinfo|spellr|spellrepall|spellu|spellundo|spellw|spellwrong|sp|split|spr|sprevious|sre|srewind|sta|stag|startg|startgreplace|star|startinsert|startr|startreplace|stj|stjump|st|stop|stopi|stopinsert|sts|stselect|sun|sunhide|sunm|sunmap|sus|suspend|sv|sview|syncbind|t|tab|tabc|tabclose|tabd|tabdo|tabe|tabedit|tabf|tabfind|tabfir|tabfirst|tabl|tablast|tabm|tabmove|tabnew|tabn|tabnext|tabN|tabNext|tabo|tabonly|tabp|tabprevious|tabr|tabrewind|tabs|ta|tag|tags|tc|tcl|tcld|tcldo|tclf|tclfile|te|tearoff|tf|tfirst|th|throw|tj|tjump|tl|tlast|tm|tm|tmenu|tn|tnext|tN|tNext|to|topleft|tp|tprevious|tr|trewind|try|ts|tselect|tu|tu|tunmenu|una|unabbreviate|u|undo|undoj|undojoin|undol|undolist|unh|unhide|unlet|unlo|unlockvar|unm|unmap|up|update|verb|verbose|ve|version|vert|vertical|vie|view|vim|vimgrep|vimgrepa|vimgrepadd|vi|visual|viu|viusage|vmapc|vmapclear|vne|vnew|vs|vsplit|vu|vunmap|wa|wall|wh|while|winc|wincmd|windo|winp|winpos|win|winsize|wn|wnext|wN|wNext|wp|wprevious|wq|wqa|wqall|w|write|ws|wsverb|wv|wviminfo|X|xa|xall|x|xit|xm|xmap|xmapc|xmapclear|xme|xmenu|XMLent|XMLns|xn|xnoremap|xnoreme|xnoremenu|xu|xunmap|y|yank)\b/,builtin:/\b(?:autocmd|acd|ai|akm|aleph|allowrevins|altkeymap|ambiwidth|ambw|anti|antialias|arab|arabic|arabicshape|ari|arshape|autochdir|autoindent|autoread|autowrite|autowriteall|aw|awa|background|backspace|backup|backupcopy|backupdir|backupext|backupskip|balloondelay|ballooneval|balloonexpr|bdir|bdlay|beval|bex|bexpr|bg|bh|bin|binary|biosk|bioskey|bk|bkc|bomb|breakat|brk|browsedir|bs|bsdir|bsk|bt|bufhidden|buflisted|buftype|casemap|ccv|cdpath|cedit|cfu|ch|charconvert|ci|cin|cindent|cink|cinkeys|cino|cinoptions|cinw|cinwords|clipboard|cmdheight|cmdwinheight|cmp|cms|columns|com|comments|commentstring|compatible|complete|completefunc|completeopt|consk|conskey|copyindent|cot|cpo|cpoptions|cpt|cscopepathcomp|cscopeprg|cscopequickfix|cscopetag|cscopetagorder|cscopeverbose|cspc|csprg|csqf|cst|csto|csverb|cuc|cul|cursorcolumn|cursorline|cwh|debug|deco|def|define|delcombine|dex|dg|dict|dictionary|diff|diffexpr|diffopt|digraph|dip|dir|directory|dy|ea|ead|eadirection|eb|ed|edcompatible|ef|efm|ei|ek|enc|encoding|endofline|eol|ep|equalalways|equalprg|errorbells|errorfile|errorformat|esckeys|et|eventignore|expandtab|exrc|fcl|fcs|fdc|fde|fdi|fdl|fdls|fdm|fdn|fdo|fdt|fen|fenc|fencs|fex|ff|ffs|fileencoding|fileencodings|fileformat|fileformats|fillchars|fk|fkmap|flp|fml|fmr|foldcolumn|foldenable|foldexpr|foldignore|foldlevel|foldlevelstart|foldmarker|foldmethod|foldminlines|foldnestmax|foldtext|formatexpr|formatlistpat|formatoptions|formatprg|fp|fs|fsync|ft|gcr|gd|gdefault|gfm|gfn|gfs|gfw|ghr|gp|grepformat|grepprg|gtl|gtt|guicursor|guifont|guifontset|guifontwide|guiheadroom|guioptions|guipty|guitablabel|guitabtooltip|helpfile|helpheight|helplang|hf|hh|hi|hidden|highlight|hk|hkmap|hkmapp|hkp|hl|hlg|hls|hlsearch|ic|icon|iconstring|ignorecase|im|imactivatekey|imak|imc|imcmdline|imd|imdisable|imi|iminsert|ims|imsearch|inc|include|includeexpr|incsearch|inde|indentexpr|indentkeys|indk|inex|inf|infercase|insertmode|isf|isfname|isi|isident|isk|iskeyword|isprint|joinspaces|js|key|keymap|keymodel|keywordprg|km|kmp|kp|langmap|langmenu|laststatus|lazyredraw|lbr|lcs|linebreak|lines|linespace|lisp|lispwords|listchars|loadplugins|lpl|lsp|lz|macatsui|magic|makeef|makeprg|matchpairs|matchtime|maxcombine|maxfuncdepth|maxmapdepth|maxmem|maxmempattern|maxmemtot|mco|mef|menuitems|mfd|mh|mis|mkspellmem|ml|mls|mm|mmd|mmp|mmt|modeline|modelines|modifiable|modified|more|mouse|mousef|mousefocus|mousehide|mousem|mousemodel|mouses|mouseshape|mouset|mousetime|mp|mps|msm|mzq|mzquantum|nf|nrformats|numberwidth|nuw|odev|oft|ofu|omnifunc|opendevice|operatorfunc|opfunc|osfiletype|pa|para|paragraphs|paste|pastetoggle|patchexpr|patchmode|path|pdev|penc|pex|pexpr|pfn|ph|pheader|pi|pm|pmbcs|pmbfn|popt|preserveindent|previewheight|previewwindow|printdevice|printencoding|printexpr|printfont|printheader|printmbcharset|printmbfont|printoptions|prompt|pt|pumheight|pvh|pvw|qe|quoteescape|readonly|remap|report|restorescreen|revins|rightleft|rightleftcmd|rl|rlc|ro|rs|rtp|ruf|ruler|rulerformat|runtimepath|sbo|sc|scb|scr|scroll|scrollbind|scrolljump|scrolloff|scrollopt|scs|sect|sections|secure|sel|selection|selectmode|sessionoptions|sft|shcf|shellcmdflag|shellpipe|shellquote|shellredir|shellslash|shelltemp|shelltype|shellxquote|shiftround|shiftwidth|shm|shortmess|shortname|showbreak|showcmd|showfulltag|showmatch|showmode|showtabline|shq|si|sidescroll|sidescrolloff|siso|sj|slm|smartcase|smartindent|smarttab|smc|smd|softtabstop|sol|spc|spell|spellcapcheck|spellfile|spelllang|spellsuggest|spf|spl|splitbelow|splitright|sps|sr|srr|ss|ssl|ssop|stal|startofline|statusline|stl|stmp|su|sua|suffixes|suffixesadd|sw|swapfile|swapsync|swb|swf|switchbuf|sws|sxq|syn|synmaxcol|syntax|tabline|tabpagemax|tabstop|tagbsearch|taglength|tagrelative|tagstack|tal|tb|tbi|tbidi|tbis|tbs|tenc|term|termbidi|termencoding|terse|textauto|textmode|textwidth|tgst|thesaurus|tildeop|timeout|timeoutlen|title|titlelen|titleold|titlestring|toolbar|toolbariconsize|top|tpm|tsl|tsr|ttimeout|ttimeoutlen|ttm|tty|ttybuiltin|ttyfast|ttym|ttymouse|ttyscroll|ttytype|tw|tx|uc|ul|undolevels|updatecount|updatetime|ut|vb|vbs|vdir|verbosefile|vfile|viewdir|viewoptions|viminfo|virtualedit|visualbell|vop|wak|warn|wb|wc|wcm|wd|weirdinvert|wfh|wfw|whichwrap|wi|wig|wildchar|wildcharm|wildignore|wildmenu|wildmode|wildoptions|wim|winaltkeys|window|winfixheight|winfixwidth|winheight|winminheight|winminwidth|winwidth|wiv|wiw|wm|wmh|wmnu|wmw|wop|wrap|wrapmargin|wrapscan|writeany|writebackup|writedelay|ww|noacd|noai|noakm|noallowrevins|noaltkeymap|noanti|noantialias|noar|noarab|noarabic|noarabicshape|noari|noarshape|noautochdir|noautoindent|noautoread|noautowrite|noautowriteall|noaw|noawa|nobackup|noballooneval|nobeval|nobin|nobinary|nobiosk|nobioskey|nobk|nobl|nobomb|nobuflisted|nocf|noci|nocin|nocindent|nocompatible|noconfirm|noconsk|noconskey|nocopyindent|nocp|nocscopetag|nocscopeverbose|nocst|nocsverb|nocuc|nocul|nocursorcolumn|nocursorline|nodeco|nodelcombine|nodg|nodiff|nodigraph|nodisable|noea|noeb|noed|noedcompatible|noek|noendofline|noeol|noequalalways|noerrorbells|noesckeys|noet|noex|noexpandtab|noexrc|nofen|nofk|nofkmap|nofoldenable|nogd|nogdefault|noguipty|nohid|nohidden|nohk|nohkmap|nohkmapp|nohkp|nohls|noic|noicon|noignorecase|noim|noimc|noimcmdline|noimd|noincsearch|noinf|noinfercase|noinsertmode|nois|nojoinspaces|nojs|nolazyredraw|nolbr|nolinebreak|nolisp|nolist|noloadplugins|nolpl|nolz|noma|nomacatsui|nomagic|nomh|noml|nomod|nomodeline|nomodifiable|nomodified|nomore|nomousef|nomousefocus|nomousehide|nonu|nonumber|noodev|noopendevice|nopaste|nopi|nopreserveindent|nopreviewwindow|noprompt|nopvw|noreadonly|noremap|norestorescreen|norevins|nori|norightleft|norightleftcmd|norl|norlc|noro|nors|noru|noruler|nosb|nosc|noscb|noscrollbind|noscs|nosecure|nosft|noshellslash|noshelltemp|noshiftround|noshortname|noshowcmd|noshowfulltag|noshowmatch|noshowmode|nosi|nosm|nosmartcase|nosmartindent|nosmarttab|nosmd|nosn|nosol|nospell|nosplitbelow|nosplitright|nospr|nosr|nossl|nosta|nostartofline|nostmp|noswapfile|noswf|nota|notagbsearch|notagrelative|notagstack|notbi|notbidi|notbs|notermbidi|noterse|notextauto|notextmode|notf|notgst|notildeop|notimeout|notitle|noto|notop|notr|nottimeout|nottybuiltin|nottyfast|notx|novb|novisualbell|nowa|nowarn|nowb|noweirdinvert|nowfh|nowfw|nowildmenu|nowinfixheight|nowinfixwidth|nowiv|nowmnu|nowrap|nowrapscan|nowrite|nowriteany|nowritebackup|nows|invacd|invai|invakm|invallowrevins|invaltkeymap|invanti|invantialias|invar|invarab|invarabic|invarabicshape|invari|invarshape|invautochdir|invautoindent|invautoread|invautowrite|invautowriteall|invaw|invawa|invbackup|invballooneval|invbeval|invbin|invbinary|invbiosk|invbioskey|invbk|invbl|invbomb|invbuflisted|invcf|invci|invcin|invcindent|invcompatible|invconfirm|invconsk|invconskey|invcopyindent|invcp|invcscopetag|invcscopeverbose|invcst|invcsverb|invcuc|invcul|invcursorcolumn|invcursorline|invdeco|invdelcombine|invdg|invdiff|invdigraph|invdisable|invea|inveb|inved|invedcompatible|invek|invendofline|inveol|invequalalways|inverrorbells|invesckeys|invet|invex|invexpandtab|invexrc|invfen|invfk|invfkmap|invfoldenable|invgd|invgdefault|invguipty|invhid|invhidden|invhk|invhkmap|invhkmapp|invhkp|invhls|invhlsearch|invic|invicon|invignorecase|invim|invimc|invimcmdline|invimd|invincsearch|invinf|invinfercase|invinsertmode|invis|invjoinspaces|invjs|invlazyredraw|invlbr|invlinebreak|invlisp|invlist|invloadplugins|invlpl|invlz|invma|invmacatsui|invmagic|invmh|invml|invmod|invmodeline|invmodifiable|invmodified|invmore|invmousef|invmousefocus|invmousehide|invnu|invnumber|invodev|invopendevice|invpaste|invpi|invpreserveindent|invpreviewwindow|invprompt|invpvw|invreadonly|invremap|invrestorescreen|invrevins|invri|invrightleft|invrightleftcmd|invrl|invrlc|invro|invrs|invru|invruler|invsb|invsc|invscb|invscrollbind|invscs|invsecure|invsft|invshellslash|invshelltemp|invshiftround|invshortname|invshowcmd|invshowfulltag|invshowmatch|invshowmode|invsi|invsm|invsmartcase|invsmartindent|invsmarttab|invsmd|invsn|invsol|invspell|invsplitbelow|invsplitright|invspr|invsr|invssl|invsta|invstartofline|invstmp|invswapfile|invswf|invta|invtagbsearch|invtagrelative|invtagstack|invtbi|invtbidi|invtbs|invtermbidi|invterse|invtextauto|invtextmode|invtf|invtgst|invtildeop|invtimeout|invtitle|invto|invtop|invtr|invttimeout|invttybuiltin|invttyfast|invtx|invvb|invvisualbell|invwa|invwarn|invwb|invweirdinvert|invwfh|invwfw|invwildmenu|invwinfixheight|invwinfixwidth|invwiv|invwmnu|invwrap|invwrapscan|invwrite|invwriteany|invwritebackup|invws|t_AB|t_AF|t_al|t_AL|t_bc|t_cd|t_ce|t_Ce|t_cl|t_cm|t_Co|t_cs|t_Cs|t_CS|t_CV|t_da|t_db|t_dl|t_DL|t_EI|t_F1|t_F2|t_F3|t_F4|t_F5|t_F6|t_F7|t_F8|t_F9|t_fs|t_IE|t_IS|t_k1|t_K1|t_k2|t_k3|t_K3|t_k4|t_K4|t_k5|t_K5|t_k6|t_K6|t_k7|t_K7|t_k8|t_K8|t_k9|t_K9|t_KA|t_kb|t_kB|t_KB|t_KC|t_kd|t_kD|t_KD|t_ke|t_KE|t_KF|t_KG|t_kh|t_KH|t_kI|t_KI|t_KJ|t_KK|t_kl|t_KL|t_kN|t_kP|t_kr|t_ks|t_ku|t_le|t_mb|t_md|t_me|t_mr|t_ms|t_nd|t_op|t_RI|t_RV|t_Sb|t_se|t_Sf|t_SI|t_so|t_sr|t_te|t_ti|t_ts|t_ue|t_us|t_ut|t_vb|t_ve|t_vi|t_vs|t_WP|t_WS|t_xs|t_ZH|t_ZR)\b/,number:/\b(?:0x[\da-f]+|\d+(?:\.\d+)?)\b/i,operator:/\|\||&&|[-+.]=?|[=!](?:[=~][#?]?)?|[<>]=?[#?]?|[*\/%?]|\b(?:is(?:not)?)\b/,punctuation:/[{}[\](),;:]/}; 39 | Prism.languages.wiki=Prism.languages.extend("markup",{"block-comment":{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0,alias:"comment"},heading:{pattern:/^(=+).+?\1/m,inside:{punctuation:/^=+|=+$/,important:/.+/}},emphasis:{pattern:/('{2,5}).+?\1/,inside:{"bold italic":{pattern:/(''''').+?(?=\1)/,lookbehind:!0},bold:{pattern:/(''')[^'](?:.*?[^'])?(?=\1)/,lookbehind:!0},italic:{pattern:/('')[^'](?:.*?[^'])?(?=\1)/,lookbehind:!0},punctuation:/^''+|''+$/}},hr:{pattern:/^-{4,}/m,alias:"punctuation"},url:[/ISBN +(?:97[89][ -]?)?(?:\d[ -]?){9}[\dx]\b|(?:RFC|PMID) +\d+/i,/\[\[.+?\]\]|\[.+?\]/],variable:[/__[A-Z]+__/,/\{{3}.+?\}{3}/,/\{\{.+?}}/],symbol:[/^#redirect/im,/~{3,5}/],"table-tag":{pattern:/((?:^|[|!])[|!])[^|\r\n]+\|(?!\|)/m,lookbehind:!0,inside:{"table-bar":{pattern:/\|$/,alias:"punctuation"},rest:Prism.languages.markup.tag.inside}},punctuation:/^(?:\{\||\|\}|\|-|[*#:;!|])|\|\||!!/m}),Prism.languages.insertBefore("wiki","tag",{nowiki:{pattern:/<(nowiki|pre|source)\b[\s\S]*?>[\s\S]*?<\/\1>/i,inside:{tag:{pattern:/<(?:nowiki|pre|source)\b[\s\S]*?>|<\/(?:nowiki|pre|source)>/i,inside:Prism.languages.markup.tag.inside}}}}); 40 | !function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add("complete",function(e){if(e.code){var t=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,"")),s.test(t.className)||(t.className+=" line-numbers");var n,a=e.code.match(/\n(?!$)/g),l=a?a.length+1:1,r=new Array(l+1);r=r.join(""),n=document.createElement("span"),n.setAttribute("aria-hidden","true"),n.className="line-numbers-rows",n.innerHTML=r,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(n)}}})}(); 41 | !function(){if(("undefined"==typeof self||self.Prism)&&("undefined"==typeof global||global.Prism)){var i=/\b([a-z]{3,7}:\/\/|tel:)[\w\-+%~\/.:#=?&]+/,n=/\b\S+@[\w.]+[a-z]{2}/,e=/\[([^\]]+)]\(([^)]+)\)/,t=["comment","url","attr-value","string"];Prism.plugins.autolinker={processGrammar:function(a){a&&!a["url-link"]&&(Prism.languages.DFS(a,function(a,r,l){t.indexOf(l)>-1&&"Array"!==Prism.util.type(r)&&(r.pattern||(r=this[a]={pattern:r}),r.inside=r.inside||{},"comment"==l&&(r.inside["md-link"]=e),"attr-value"==l?Prism.languages.insertBefore("inside","punctuation",{"url-link":i},r):r.inside["url-link"]=i,r.inside["email-link"]=n)}),a["url-link"]=i,a["email-link"]=n)}},Prism.hooks.add("before-highlight",function(i){Prism.plugins.autolinker.processGrammar(i.grammar)}),Prism.hooks.add("wrap",function(i){if(/-link$/.test(i.type)){i.tag="a";var n=i.content;if("email-link"==i.type&&0!=n.indexOf("mailto:"))n="mailto:"+n;else if("md-link"==i.type){var t=i.content.match(e);n=t[2],i.content=t[1]}i.attributes.href=n}})}}(); 42 | -------------------------------------------------------------------------------- /Public/static/js/search.js: -------------------------------------------------------------------------------- 1 | var term = $("#search-data").data("searchTerm"); 2 | 3 | if(term.length !== 0){ 4 | var options = { 5 | "separateWordSearch": false, 6 | 7 | }; 8 | $('.card-body').mark(term, options); 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | SteamPress 3 |

4 |

SteamPress Example Site

5 |

6 | 7 | Language 8 | 9 | 10 | Build Status 11 | 12 | 13 | Code Coverage 14 | 15 | 16 | MIT License 17 | 18 |

19 | 20 | This is an example site for using [SteamPress](https://github.com/brokenhandsio/SteamPress) with your Vapor website. You can also just use it as your blog! This example site uses Bootstrap 4 for styling so should be fairly easy to make it look how you want. It also uses Markdown to render the blog posts, and has Syntax highlighting for code (obviously!). This site can be viewed at https://www.steampress.io/. 21 | 22 | This site also provides a good example for all the Leaf files you will need, and the parameters they are given, as well as what you need to send to SteamPress to be create users and posts etc. 23 | 24 | # Features 25 | 26 | * Code syntax highlighting 27 | * WYSIWYG Markdown editor 28 | * Tagging selection 29 | * Content Security Policy and other security headers 30 | * Embeddable tweets 31 | * Facebook/Twitter share buttons 32 | * Blog post editor auto saving 33 | * Open Graph/Twitter Card support 34 | * Blog Post comments, powered by Disqus 35 | * Custom 404 error page 36 | 37 | # Usage 38 | 39 | To try out (you will need Docker installed): 40 | 41 | ```bash 42 | git clone https://github.com/brokenhandsio/SteamPressExample.git 43 | cd SteamPressExample 44 | ./setupDB.sh 45 | swift run 46 | ``` 47 | 48 | This will create a site at http://localhost:8080. The blog can be found at http://localhost:8080/blog/ and you can login at http://localhost:8080/blog/admin/. The first time you visit the login a user will be created and the details printed to the console. 49 | -------------------------------------------------------------------------------- /Resources/Views/404.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | Oops....Wrong Turn! | SteamPress 3 | } 4 | 5 | #set("body") { 6 |
7 | 8 |

Oops....Wrong Turn!

9 | 10 |
11 | 12 | 13 |
14 | 15 |

Looks like you've taken a wrong turn...we couldn't find the page you were looking for! Search the blog 16 | above, use the menu to navigate or you may find some of the links below useful:

17 | 18 | 24 | 25 |
26 | } 27 | 28 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/about.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | About | SteamPress Example 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | #if(pageInformation.siteTwitterHandle) { 19 | 20 | } 21 | } 22 | 23 | #set("body") { 24 | 25 |
26 | 27 |

About SteamPress

28 | 29 |

30 | SteamPress, is an open 31 | source blog engine written in Swift and built on top of the 32 | Vapor framework. 33 |

34 | 35 |

36 | It is designed to be simple to include into any website that is built using Vapor using 37 | just the Vapor Provider. It can be added to a path in a site or can even be used as the main 38 | site itself. 39 |

40 | 41 |

42 | There is an example site 43 | on Github that 44 | demonstrates how to integrate SteamPress into a site, including all the Leaf templates and how 45 | to use them. It is a good starting place to clone and get up and running quickly. This site 46 | is actually the example site deployed! 47 |

48 | 49 |

50 | The example site also includes shows how to integrate 51 | Vapor Security 52 | Headers and how to have custom error pages in Vapor, using Leaf Error Middleare. 53 |

54 | 55 | 56 |
57 | 58 | } 59 | 60 | #set("scripts") { 61 | #if(pageInformation.googleAnalyticsIdentifier) { 62 |
63 | 64 | } 65 | } 66 | 67 | #embed("base") 68 | -------------------------------------------------------------------------------- /Resources/Views/base.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #embed("headContent") 6 | 7 | #get(head) 8 | 9 | 10 | 11 |
12 | 13 | 48 | 49 | #get(body) 50 | 51 |
52 | 53 | #embed("footerAndScripts") 54 | #get(scripts) 55 | 56 | 57 | -------------------------------------------------------------------------------- /Resources/Views/blog/admin/createPost.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | #if(editing) { Edit } else { Create } Blog Post | Blog Admin | SteamPress 3 | 4 | 5 | 6 | } 7 | 8 | #set("scripts") { 9 | 10 | 11 | 12 | 13 | } 14 | 15 | #set("body") { 16 | 17 |
18 | 19 | #if(editing) { 20 |

Edit Your Blog Post

21 | } else { 22 |

Create A Blog Post

23 | } 24 | 25 | #if(errors) { 26 | #for(error in errors) { 27 | 30 | } 31 | } 32 | 33 | #if(editing) { 34 |
35 | } 36 | 37 |
38 | #if(editing) { 39 | 46 | } 47 | #if(editing) { 48 |
49 | 50 |
#if(post.published) {Published} else {Draft}
51 |
52 | } 53 |
54 | 55 | 56 |
57 | 58 |
59 | 60 |
61 |
62 | #(postPathPrefix) 63 |
64 | 65 |
66 |
67 | 68 |
69 | 70 | 71 |
72 | 73 |
74 | 75 | 80 | 81 | Tags can be added to your posts as comma-separated entries. 82 | 83 |
84 | 85 |
86 | #if(draft) { 87 | 88 | } 89 | 90 | #if(editing) { 91 | Cancel 92 | } 93 | 94 | 95 | You can style your blog posts with Markdown 96 | 97 |
98 | 99 |
100 | 101 |
102 | } 103 | 104 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/admin/createUser.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | #if(editing) {Edit } else {Create New } User | Blog Admin | SteamPress 3 | } 4 | 5 | #set("scripts") { 6 | 7 | } 8 | 9 | #set("body") { 10 | 11 | #if(editing) { 12 |
13 | } 14 | 15 |
16 | 17 |

#if(editing) {Edit } else {Create A New } User

18 | 19 | #if(errors) { 20 | #for(error in errors) { 21 | 24 | } 25 | } 26 | 27 |
28 |
29 | 30 | 31 |
32 | Please enter a name 33 |
34 |
35 | 36 |
37 | 38 | 39 |
40 | Please enter a username 41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 | 49 | 50 | 51 |
52 |
53 | Please enter a valid password 54 |
55 | #if(editing) { 56 | Leave the password blank if you don't want to change the password 57 | } 58 |
59 | 60 |
61 | 62 | 63 |
64 | Please confirm your password 65 |
66 |
67 | 68 |
69 | 73 |
74 | 75 |
76 | 77 | 78 |
79 | 80 |
81 | 82 |
83 |
84 | @ 85 |
86 | 87 |
88 |
89 | 90 |
91 | 92 | 93 |
94 | 95 |
96 | 97 | 98 |
99 | 100 |
101 | 102 | #if(editing) { 103 | Cancel 104 | } 105 | 106 | * Required Field 107 | 108 |
109 |
110 | 111 |
112 | 113 | 114 | 129 | } 130 | 131 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/admin/index.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | Admin | Blog Admin | SteamPress 3 | } 4 | 5 | #set("scripts") { 6 | 7 | } 8 | 9 | #set("body") { 10 | 11 |
12 | 13 |

Blog Admin

14 | 15 | #if(errors) { 16 | #for(error in errors) { 17 | 20 | } 21 | } 22 |

Welcome to the Blog Admin page

23 | 24 |
25 |

Published Posts

26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | #if(count(publishedPosts) > 0) { 40 | #for(post in publishedPosts) { 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | } 50 | } else { 51 | 52 | 53 | 54 | } 55 | 56 | 57 |
Date PublishedTitleAuthorLast EditedEditDelete
#(post.createdDateNumeric)#(post.title)#(post.authorName)#if(post.lastEdited) {#(post.lastEditedDateNumeric)} else { N/A }Edit
There haven't been any posts published yet!
58 | 59 | #if(count(draftPosts) > 0) { 60 |

Drafts

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | #for(post in draftPosts) { 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | } 81 | 82 | 83 |
Last UpdatedTitleAuthorEditDelete
#(post.createdDateNumeric)#(post.title)#(post.authorName)Edit
84 | } 85 | 86 | Create New Post 87 |
88 | 89 |
90 |

Users

91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | #for(user in users) { 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | } 113 | 114 | 115 |
#NameUsernameEditDelete
#(user.userID)#(user.name)#(user.username)Edit
116 | 117 | Create New User 118 | 119 |
120 | 121 |
122 | } 123 | 124 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/admin/login.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | Login | Blog Admin | SteamPress 3 | } 4 | 5 | #set("scripts") { 6 | 7 | } 8 | 9 | #set("body") { 10 | 11 |
12 | 13 |

Blog Login

14 | 15 | #if(errors) { 16 | #for(error in errors) { 17 | 20 | } 21 | } 22 | 23 | #if(loginWarning) { 24 | 27 | } 28 | 29 | 44 | 45 |
46 | } 47 | 48 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/admin/resetPassword.leaf: -------------------------------------------------------------------------------- 1 | #extend("base") 2 | 3 | #export("head") { 4 | Reset Your Password | Blog Admin | SteamPress 5 | } 6 | 7 | #export("scripts") { 8 | 9 | } 10 | 11 | #export("body") { 12 | 13 |
14 | 15 |

Reset Your Password

16 | 17 | #if(errors) { 18 | #loop(errors, "error") { 19 | 22 | } 23 | } 24 | 25 | 28 | 29 |
30 |
31 | 32 | 33 |
34 | Please provide a new password. It must contain a least 8 characters. 35 |
36 |
37 |
38 | 39 | 40 |
41 | Please confirm your new password. 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 | } 51 | -------------------------------------------------------------------------------- /Resources/Views/blog/authors.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | All Authors | Blog | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | #if(pageInformation.siteTwitterHandle) { 19 | 20 | } 21 | } 22 | 23 | #set("body") { 24 | 25 |
26 | 27 |

All SteamPress Blog's Authors

28 | 29 |
30 | 31 | #if(count(authors) > 0) { 32 | #for(author in authors) { 33 |
34 |
35 |
36 |

#(author.name)

37 |
#(author.username)
38 |
39 | #if(author.tagline) { 40 |

#(author.tagline)

41 | } 42 | View #(author.name)'s Profile 43 |
44 |
45 | 48 |
49 |
50 | } 51 | 52 | } else { 53 |

There haven't been any authors yet!

54 | } 55 |
56 | 57 |
58 | } 59 | 60 | #set("scripts") { 61 | #if(pageInformation.googleAnalyticsIdentifier) { 62 |
63 | 64 | } 65 | } 66 | 67 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/blog.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | Blog | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | #if(pageInformation.siteTwitterHandle) { 22 | 23 | } 24 | } 25 | 26 | #set("body") { 27 | 28 |
29 | 30 |

The SteamPress Blog

31 | 32 |
33 | 34 |
35 | #embed("blog/postsView") 36 |
37 | 38 |
39 |
40 |
41 |

Tags

42 |
    43 | #for(tag in tags) { 44 |
  • #(tag.name)
  • 45 | } 46 |
47 | All Tags 48 |
49 |
50 |
51 |
52 |

Authors

53 | 58 | All Authors 59 |
60 |
61 |
62 |
63 |

Subcribe

64 | 68 |
69 |
70 |
71 | 72 |
73 | 74 |
75 | } 76 | 77 | #set("scripts") { 78 | 79 | 80 | 81 | #if(pageInformation.disqusName) { 82 | 83 | } 84 | #if(pageInformation.googleAnalyticsIdentifier) { 85 |
86 | 87 | } 88 | } 89 | 90 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/post.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | #(post.title) | Blog | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | #if(post.lastEditedDateNumeric) { } 17 | 18 | #for(tag in post.tags) { 19 | 20 | } 21 | 22 | #if(post.postImage) { 23 | 24 | } else { 25 | 26 | 27 | 28 | 29 | } 30 | #if(post.postImageAlt) { 31 | 32 | } 33 | #if(pageInformation.siteTwitterHandle) { 34 | 35 | } 36 | #if(author.twitterHandle) { 37 | 38 | } 39 | } 40 | 41 | #set("body") { 42 | 43 |
44 | 45 | #if(!post.published) { 46 |
DRAFT POST
47 | } 48 | 49 |

#(post.title)

50 | 51 |

Written by #(author.name) on #(post.createdDateLong)#if(post.lastEdited) {. Last edited on #(post.lastEditedDateLong).}

52 | 53 |
54 | #markdown(post.contents) 55 |
56 | 57 | 62 | 63 | 71 | 72 | #if(post.published) { 73 | #if(pageInformation.disqusName) { 74 |
75 |
76 | 77 | } 78 | } 79 |
80 | } 81 | 82 | #set("scripts") { 83 | 84 |
85 | 86 | 87 | 88 | #if(pageInformation.disqusName) { 89 | 90 | } 91 | #if(pageInformation.googleAnalyticsIdentifier) { 92 |
93 | 94 | } 95 | } 96 | 97 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/postsView.leaf: -------------------------------------------------------------------------------- 1 |
2 | #if(count(posts) > 0) { 3 | #for(post in posts) { 4 |
5 |
6 |

#(post.title)

7 |
#(post.createdDateLong) by #(post.authorName)
8 |
9 | #markdown(post.longSnippet) 10 |
11 |
12 | Continue Reading 13 |
14 |
15 | 27 |
28 | } 29 | #paginator() 30 | } else { 31 |

There haven't been any blog posts yet!

32 | } 33 |
-------------------------------------------------------------------------------- /Resources/Views/blog/profile.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | #(author.name)'s Profile | Blog | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | #if(author.profilePicture) { 14 | 15 | 16 | } else { 17 | 18 | 19 | 20 | 21 | } 22 | 23 | #if(pageInformation.siteTwitterHandle) { 24 | 25 | } 26 | 27 | #if(author.twitterHandle) { 28 | 29 | } 30 | 31 | 32 | 33 | } 34 | 35 | #set("scripts") { 36 | 37 | #if(pageInformation.disqusName) { 38 | 39 | } 40 | #if(pageInformation.googleAnalyticsIdentifier) { 41 |
42 | 43 | } 44 | } 45 | 46 | #set("body") { 47 | 48 |
49 | 50 |

#(author.name)'s Profile

51 | 52 |
53 |
54 | #if(author.profilePicture) { 55 | Profile picture for #(author.name) 56 | } 57 |

#(author.name)

58 |

#(author.username)

59 | 60 | #if(author.twitterHandle) { 61 |
62 | Twitter @#(author.twitterHandle) 63 |
64 | } 65 | 66 | #if(author.tagline) { 67 |
#(author.tagline)
68 | } 69 | 70 | #if(author.biography) { 71 |
Biography
#(author.biography)
72 | } 73 | 74 | #if(pageInformation.loggedInUser) { 75 | Edit Profile 76 | } 77 | 78 |
79 | 80 |
81 | 82 |

#(author.name)'s Posts

83 |

#if(postCount == 1) {#(postCount) post} else {#(postCount) posts}

84 | 85 | #embed("blog/postsView") 86 |
87 |
88 | 89 |
90 | 91 | } 92 | 93 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/search.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | #if(searchTerm) {Search Blog For '#(searchTerm)'} else {Blog Search} | Blog | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | #if(pageInformation.siteTwitterHandle) { 18 | 19 | } 20 | 21 | 22 | 23 | } 24 | 25 | #set("scripts") { 26 | 27 | #if(pageInformation.disqusName) { 28 | 29 | } 30 | #if(pageInformation.googleAnalyticsIdentifier) { 31 |
32 | 33 | } 34 | 35 | 36 | } 37 | 38 | #set("body") { 39 | 40 |
41 | 42 |

SteamPress Search

43 | 44 |
45 | 46 |
47 |
48 | 49 | 50 |
51 | #if(searchTerm) { 52 |

#(totalResults) #if(totalResults == 1) { result } else { results } for '#(searchTerm)'

53 | #for(post in posts) { 54 |
55 |
56 |

#(post.title)

57 |
#(post.createdDateLong) by #(post.authorName)
58 |
59 | #markdown(post.longSnippet) 60 |
61 |
62 | Continue Reading 63 |
64 |
65 | 77 |
78 | } 79 | #paginator() 80 | } 81 |
82 | 83 | #if(searchTerm) { 84 |
85 | } 86 |
87 | 88 |
89 | 90 | } 91 | 92 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/tag.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | Posts Tagged With #(tag.name) | Blog | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | #if(pageInformation.siteTwitterHandle) { 18 | 19 | } 20 | 21 | 22 | 23 | } 24 | 25 | #set("scripts") { 26 | 27 | #if(pageInformation.disqusName) { 28 | 29 | } 30 | #if(pageInformation.googleAnalyticsIdentifier) { 31 |
32 | 33 | } 34 | } 35 | 36 | #set("body") { 37 | 38 |
39 | 40 |

Posts Tagged With #(tag.name)

41 |

#if(postCount == 1) {#(postCount) post} else {#(postCount) posts}

42 | 43 |
44 | #embed("blog/postsView") 45 |
46 | 47 |
48 | 49 | } 50 | 51 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/blog/tags.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | All Tags | Blog | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | #if(pageInformation.siteTwitterHandle) { 18 | 19 | } 20 | } 21 | 22 | #set("body") { 23 | 24 |
25 | 26 |

All SteamPress Blog's Tags

27 | 28 |
29 | 30 | #if(count(tags) > 0) { 31 | #for(tag in tags) { 32 |
33 |
34 |
35 |

#(tag.name)

36 |
37 | View #(tag.name) 38 |
39 |
40 | 43 |
44 |
45 | } 46 | 47 | } else { 48 |

There haven't been any tags yet!

49 | } 50 |
51 | 52 |
53 | 54 | } 55 | 56 | #set("scripts") { 57 | #if(pageInformation.googleAnalyticsIdentifier) { 58 |
59 | 60 | } 61 | } 62 | 63 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/footerAndScripts.leaf: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Resources/Views/headContent.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Resources/Views/index.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | Home | SteamPress 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | #if(pageInformation.siteTwitterHandle) { 23 | 24 | } 25 | } 26 | 27 | #set("body") { 28 |
29 |
30 | SteamPress 31 |

An Open Source Blogging Engine written in Swift for Vapor

32 |
33 |
34 | 35 |
36 |
37 |
38 |

Latest Blog Posts

39 | 40 | #if(count(posts) > 0) { 41 | #for(post in posts) { 42 |
43 |
44 |

#(post.title)

45 | #markdown(post.shortSnippet) 46 | Read More 47 |
48 |
49 | } 50 | } else { 51 |
There haven't been any posts yet!
52 | } 53 | 54 |
55 | View the Blog 56 |
57 |
58 | 59 |
60 |

More Open Source Projects

61 | 62 |
63 | Vapor Security Headers Logo 64 |
65 |

Vapor Security Headers

66 |

Easily add common security headers to your Vapor application to help secure you and your users.

67 | View on Github 68 |
69 |
70 | 71 |
72 | Vapor OAuth Logo 73 |
74 |

Vapor OAuth

75 |

A fully spec-compliant OAuth2 Provider library built for Vapor.

76 | View on Github 77 |
78 |
79 | 80 |
81 | SteamPress Logo 82 |
83 |

SteamPress Example

84 |

An easy-to-deploy Blogging Platform for SteamPress. Contains all the code you need to get up and running with SteamPress. In fact, it is the code for this site!

85 | View on Github 86 |
87 |
88 | 89 | 92 |
93 | 94 |
95 | 96 |
97 | } 98 | 99 | #set("scripts") { 100 | 101 | #if(pageInformation.googleAnalyticsIdentifier) { 102 |
103 | 104 | } 105 | } 106 | 107 | #embed("base") -------------------------------------------------------------------------------- /Resources/Views/serverError.leaf: -------------------------------------------------------------------------------- 1 | #set("head") { 2 | Error | SteamPress 3 | } 4 | 5 | #set("body") { 6 |
7 | 8 |

#(statusMessage)

9 | 10 |

The server returned error code #(status) with the message #(statusMessage).

11 | 12 |

Please try again later or use the menu above to navigate or you may find some of the links below useful:

13 | 14 | 20 | 21 |
22 | } 23 | 24 | #embed("base") -------------------------------------------------------------------------------- /Sources/App/Models/PostWithShortSnippet.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SteamPress 3 | 4 | struct PostWithShortSnippet: Codable { 5 | var blogID: Int? 6 | var title: String 7 | var contents: String 8 | var author: Int 9 | var created: Date 10 | var lastEdited: Date? 11 | var slugUrl: String 12 | var published: Bool 13 | var shortSnippet: String 14 | } 15 | 16 | extension BlogPost { 17 | func toPostWithShortSnippet() -> PostWithShortSnippet { 18 | return PostWithShortSnippet(blogID: self.blogID, title: self.title, contents: self.contents, author: self.author, created: self.created, lastEdited: self.lastEdited, slugUrl: self.slugUrl, published: self.published, shortSnippet: self.shortSnippet()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/App/app.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | /// Creates an instance of Application. This is called from main.swift in the run target. 4 | public func app(_ env: Environment) throws -> Application { 5 | var config = Config.default() 6 | var env = env 7 | var services = Services.default() 8 | try configure(&config, &env, &services) 9 | let app = try Application(config: config, environment: env, services: services) 10 | return app 11 | } 12 | -------------------------------------------------------------------------------- /Sources/App/configure.swift: -------------------------------------------------------------------------------- 1 | import FluentPostgreSQL 2 | import Vapor 3 | import Leaf 4 | import Authentication 5 | import LeafErrorMiddleware 6 | import SteampressFluentPostgres 7 | import VaporSecurityHeaders 8 | import LeafMarkdown 9 | 10 | /// Called before your application initializes. 11 | public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws { 12 | /// Register providers first 13 | try services.register(FluentPostgreSQLProvider()) 14 | try services.register(LeafProvider()) 15 | try services.register(AuthenticationProvider()) 16 | let feedInformation = FeedInformation(title: "The SteamPress Blog", description: "SteamPress is an open-source blogging engine written for Vapor in Swift", copyright: "Released under the MIT licence", imageURL: "https://user-images.githubusercontent.com/9938337/29742058-ed41dcc0-8a6f-11e7-9cfc-680501cdfb97.png") 17 | try services.register(SteamPressFluentPostgresProvider(blogPath: "blog", feedInformation: feedInformation, postsPerPage: 5)) 18 | 19 | services.register { worker in 20 | return LeafErrorMiddleware(environment: worker.environment) 21 | } 22 | 23 | /// Register routes to the router 24 | let router = EngineRouter.default() 25 | try routes(router) 26 | services.register(router, as: Router.self) 27 | 28 | let disqusName = Environment.get("BLOG_DISQUS_NAME") ?? "*" 29 | 30 | var cspBuilder = ContentSecurityPolicy() 31 | .defaultSrc(sources: CSPKeywords.none) 32 | .scriptSrc(sources: CSPKeywords.`self`, "https://www.google-analytics.com/", "https://static.brokenhands.io", "https://cdn.jsdelivr.net/", "https://connect.facebook.net/", "https://publish.twitter.com", "cdn.syndication.twimg.com", "platform.twitter.com", "https://platform.linkedin.com", "https://ajax.googleapis.com/", "https://code.jquery.com/", "https://cdnjs.cloudflare.com/", "https://stackpath.bootstrapcdn.com", "https://\(disqusName).disqus.com/", "https://*.disquscdn.com/", "https://disqus.com/") 33 | .styleSrc(sources: CSPKeywords.`self`, "https://use.fontawesome.com", "https://cdn.jsdelivr.net/", "*.twimg.com", "platform.twitter.com", "https://stackpath.bootstrapcdn.com/", "https://*.disquscdn.com/", "https://cdnjs.cloudflare.com/ajax/libs/select2/ https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css") 34 | .imgSrc(sources: CSPKeywords.`self`, "data:", "https://static.brokenhands.io", "https://www.facebook.com", "cdn.syndication.twimg.com", "syndication.twitter.com", "*.twimg.com", "platform.twitter.com", "https://referrer.disqus.com/", "https://*.disquscdn.com/", "https://www.google-analytics.com/") 35 | .connectSrc(sources: CSPKeywords.`self`, "https://links.services.disqus.com/") 36 | .fontSrc(sources: "https://maxcdn.bootstrapcdn.com/", "https://use.fontawesome.com/") 37 | .set(value: "child-src https://disqus.com/ syndication.twitter.com platform.twitter.com www.facebook.com staticxx.facebook.com; manifest-src 'self'") 38 | .formAction(sources: CSPKeywords.`self`) 39 | .baseUri(sources: CSPKeywords.`self`) 40 | .requireSriFor(values: "script style") 41 | 42 | if env == .production || env == .testing { 43 | cspBuilder = cspBuilder.upgradeInsecureRequests().blockAllMixedContent() 44 | } 45 | 46 | if let reportURI = Environment.get("CSP_REPORT_URI") { 47 | cspBuilder = cspBuilder.reportUri(uri: reportURI) 48 | } 49 | 50 | let referrerPolicy = ReferrerPolicyConfiguration(.strictOriginWhenCrossOrigin) 51 | 52 | let securityHeadersFactory = SecurityHeadersFactory() 53 | .with(server: ServerConfiguration(value: "brokenhands.io")) 54 | .with(contentSecurityPolicy: ContentSecurityPolicyConfiguration(value: cspBuilder)) 55 | .with(referrerPolicy: referrerPolicy) 56 | services.register(securityHeadersFactory.build()) 57 | 58 | /// Register middleware 59 | var middlewares = MiddlewareConfig() // Create _empty_ middleware config 60 | middlewares.use(SecurityHeaders.self) 61 | middlewares.use(FileMiddleware.self) // Serves files from `Public/` directory 62 | middlewares.use(LeafErrorMiddleware.self) 63 | middlewares.use(BlogRememberMeMiddleware.self) 64 | middlewares.use(SessionsMiddleware.self) 65 | services.register(middlewares) 66 | 67 | // Limit the DB connections for Heroku 68 | services.register(DatabaseConnectionPoolConfig(maxConnections: 2)) 69 | 70 | // Configure a database 71 | var databases = DatabasesConfig() 72 | let databaseConfig: PostgreSQLDatabaseConfig 73 | if let url = Environment.get("DATABASE_URL") { 74 | databaseConfig = PostgreSQLDatabaseConfig(url: url, transport: .unverifiedTLS)! 75 | } else if let url = Environment.get("DB_POSTGRESQL") { 76 | databaseConfig = PostgreSQLDatabaseConfig(url: url)! 77 | } else { 78 | let hostname = Environment.get("DATABASE_HOSTNAME") ?? "localhost" 79 | let username = Environment.get("DATABASE_USER") ?? "steampress" 80 | let password = Environment.get("DATABASE_PASSWORD") ?? "password" 81 | let databaseName: String 82 | let databasePort: Int 83 | if (env == .testing) { 84 | databaseName = "steampress-test" 85 | if let testPort = Environment.get("DATABASE_PORT") { 86 | databasePort = Int(testPort) ?? 5433 87 | } else { 88 | databasePort = 5433 89 | } 90 | } else { 91 | databaseName = Environment.get("DATABASE_DB") ?? "steampress" 92 | databasePort = 5432 93 | } 94 | 95 | databaseConfig = PostgreSQLDatabaseConfig( 96 | hostname: hostname, 97 | port: databasePort, 98 | username: username, 99 | database: databaseName, 100 | password: password) 101 | } 102 | let database = PostgreSQLDatabase(config: databaseConfig) 103 | databases.add(database: database, as: .psql) 104 | services.register(databases) 105 | 106 | /// Configure migrations 107 | var migrations = MigrationConfig() 108 | migrations.add(model: BlogTag.self, database: .psql) 109 | migrations.add(model: BlogUser.self, database: .psql) 110 | migrations.add(model: BlogPost.self, database: .psql) 111 | migrations.add(model: BlogPostTagPivot.self, database: .psql) 112 | migrations.add(migration: BlogAdminUser.self, database: .psql) 113 | services.register(migrations) 114 | 115 | config.prefer(LeafRenderer.self, for: ViewRenderer.self) 116 | config.prefer(MemoryKeyedCache.self, for: KeyedCache.self) 117 | config.prefer(BCryptDigest.self, for: PasswordVerifier.self) 118 | 119 | var tags = LeafTagConfig.default() 120 | tags.use(Markdown(), as: "markdown") 121 | let paginatorTag = PaginatorTag(paginationLabel: "Blog Posts") 122 | tags.use(paginatorTag, as: PaginatorTag.name) 123 | services.register(tags) 124 | } 125 | -------------------------------------------------------------------------------- /Sources/App/routes.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import SteampressFluentPostgres 3 | import Fluent 4 | 5 | /// Register your application's routes here. 6 | public func routes(_ router: Router) throws { 7 | 8 | let authSessions = router.grouped(BlogAuthSessionsMiddleware()) 9 | 10 | authSessions.get { req -> Future in 11 | return BlogPost.query(on: req).filter(\.published == true).sort(\.created, .descending).range(0..<3).all().flatMap { posts in 12 | let pageInformation = try req.getPageInformation() 13 | let postsWithShortSnippets = posts.map { $0.toPostWithShortSnippet() } 14 | let context = HomepageContext(posts: postsWithShortSnippets, pageInformation: pageInformation) 15 | return try req.view().render("index", context) 16 | } 17 | } 18 | 19 | authSessions.get("about") { req -> Future in 20 | let pageInformation = try req.getPageInformation() 21 | let context = AboutPageContext(pageInformation: pageInformation) 22 | return try req.view().render("about", context) 23 | } 24 | } 25 | 26 | struct HomepageContext: Encodable { 27 | let posts: [PostWithShortSnippet] 28 | let pageInformation: PageInformation 29 | } 30 | 31 | struct AboutPageContext: Encodable { 32 | let aboutPage = true 33 | let pageInformation: PageInformation 34 | } 35 | 36 | struct PageInformation: Encodable { 37 | let loggedInUser: BlogUser? 38 | let twitterHandler: String? 39 | let googleAnalytics: String? 40 | let disqusName: String? 41 | let webpageURL: String 42 | } 43 | 44 | extension Request { 45 | func getPageInformation() throws -> PageInformation { 46 | let disqusName = Environment.get("BLOG_DISQUS_NAME") 47 | let siteTwitterHandle = Environment.get("BLOG_SITE_TWITTER_HANDLE") 48 | let googleAnalyticsIdentifier = Environment.get("BLOG_GOOGLE_ANALYTICS_IDENTIFIER") 49 | let loggedInUser = try self.authenticated(BlogUser.self) 50 | let webpageURL = self.http.url.absoluteString 51 | return PageInformation(loggedInUser: loggedInUser, twitterHandler: siteTwitterHandle, googleAnalytics: googleAnalyticsIdentifier, disqusName: disqusName, webpageURL: webpageURL) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | 3 | try app(.detect()).run() 4 | -------------------------------------------------------------------------------- /Tests/AppTests/SteamPressExampleTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import App 3 | import Vapor 4 | import SteamPress 5 | 6 | class SteamPressExampleTests: XCTestCase { 7 | 8 | // MARK: - Properties 9 | var app: Application! 10 | 11 | // MARK: - Overrides 12 | override func setUp() { 13 | app = try! getApp() 14 | } 15 | 16 | // MARK: - Tests 17 | func testSecurityHeadersSetupCorrectly() throws { 18 | let response = try getResponse(to: "/", on: app) 19 | 20 | XCTAssertEqual(response.http.status, .ok) 21 | XCTAssertEqual(response.http.headers["X-Frame-Options"].first, "DENY") 22 | } 23 | 24 | func testThatAboutPageRouteAdded() throws { 25 | let response = try getResponse(to: "/about", on: app) 26 | 27 | XCTAssertEqual(response.http.status, .ok) 28 | } 29 | 30 | func testThatSteamPressSetUp() throws { 31 | let response = try getResponse(to: "/blog", on: app) 32 | 33 | XCTAssertEqual(response.http.status, .ok) 34 | } 35 | 36 | func testGetSnippetPost() throws { 37 | let post = try BlogPost(title: "some title", contents: "some contents", author: BlogUser(userID: 1, name: "Name", username: "username", password: "password", profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil), creationDate: Date(), slugUrl: "some-slug", published: true) 38 | let snippet = post.toPostWithShortSnippet() 39 | 40 | XCTAssertEqual(snippet.title, post.title) 41 | } 42 | 43 | // MARK: - Helpers 44 | func getApp() throws -> Application { 45 | var config = Config.default() 46 | var services = Services.default() 47 | var env = Environment.testing 48 | try App.configure(&config, &env, &services) 49 | let app = try Application(config: config, environment: env, services: services) 50 | return app 51 | } 52 | 53 | func getResponse(to path: String, on app: Application) throws -> Response { 54 | let responder = try app.make(Responder.self) 55 | let request = HTTPRequest(method: .GET, url: URL(string: path)!) 56 | let wrappedRequest = Request(http: request, using: app) 57 | return try responder.respond(to: wrappedRequest).wait() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /resetDB.sh: -------------------------------------------------------------------------------- 1 | docker stop steampress && docker rm steampress && sh setupDB.sh 2 | -------------------------------------------------------------------------------- /setupDB.sh: -------------------------------------------------------------------------------- 1 | docker run --name steampress -e POSTGRES_DB=steampress -e POSTGRES_USER=steampress -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres 2 | -------------------------------------------------------------------------------- /setupTestDB.sh: -------------------------------------------------------------------------------- 1 | docker run --name steampress-test -e POSTGRES_DB=steampress-test -e POSTGRES_USER=steampress -e POSTGRES_PASSWORD=password -p 5433:5432 -d postgres 2 | --------------------------------------------------------------------------------