├── .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 |
--------------------------------------------------------------------------------
/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+""+i.tag+">"},!_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:/(