Add code from previous version

This commit is contained in:
Kacper Donat 2020-04-11 21:55:18 +02:00
parent 7b532690ad
commit 4cf445df4d
90 changed files with 9982 additions and 90 deletions

7
.gitignore vendored
View File

@ -8,3 +8,10 @@
/var/
/vendor/
###< symfony/framework-bundle ###
###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###

View File

@ -0,0 +1,9 @@
@font-face {
font-family: 'JetBrains Mono Medium';
src: url('JetBrainsMono/web/eot/JetBrainsMono-Medium.eot');
src: url('JetBrainsMono/web/eot/JetBrainsMono-Medium.eot?#iefix') format('embedded-opentype'),
url('JetBrainsMono/web/woff/JetBrainsMono-Medium.woff') format('woff')
;
font-weight: normal;
font-style: normal;
}

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

BIN
assets/img/favicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

BIN
assets/img/kadet.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/img/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/img/overlay.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

35
assets/sass/_bootstrap.scss Executable file
View File

@ -0,0 +1,35 @@
@import "../../node_modules/bootstrap/scss/functions";
@import "../../node_modules/bootstrap/scss/variables";
$theme-colors: map_merge($theme-colors, (
primary: $color-accent,
dark: $color-dark
));
$navbar-dark-color: $color-accent;
$link-color: darken(map_get($theme-colors, 'primary'), 10%);
@import "../../node_modules/bootstrap/scss/bootstrap";
.navbar-nav {
> .nav-item {
font-family: $font-family-monospace;
> a {
padding-left: 1rem !important;
padding-right: 1rem !important;
}
}
}
a.navbar-brand {
padding-bottom: 0.25rem;
padding-top: 0.25rem;
> img {
height: 2rem;
}
}
.btn-primary, .btn-primary:hover, .btn-primary:active { color: white; }
a, button {
cursor: pointer;
}

8
assets/sass/_docs.scss Executable file
View File

@ -0,0 +1,8 @@
.chapter--navigation {
display: flex;
justify-content: flex-end;
.nav--prev {
margin-right: auto;
}
}

59
assets/sass/_keylighter.scss Executable file
View File

@ -0,0 +1,59 @@
$kl-prefix: "kl-";
@import "../../vendor/kadet/keylighter/Styles/Html/keylighter";
pre.keylighter {
background-color: #181818;
color: $foreground;
font-weight: 100;
border-radius: 0;
border: none;
font-size: 10pt;
}
pre.keylighter:not(.inline), .try-it textarea {
background: #181818 url("../img/overlay.png") no-repeat right 30px bottom 0;
padding: 25px !important;
}
pre.keylighter.inline {
display: inline-block;
padding: .25rem .5rem;
vertical-align: baseline;
margin-bottom: 0;
}
code.keylighter {
background-color: #181818;
color: $foreground;
}
.keylighter.inline {
padding: .25rem .5rem;
}
code {
color: $background;
background: $gray-100;
white-space: pre;
}
.close {
float: none;
font-size: inherit;
font-weight: inherit;
line-height: inherit;
color: inherit;
text-shadow: none;
opacity: inherit;
}
.close:focus, .close:hover {
color: inherit;
text-decoration: inherit;
opacity: inherit;
}
$kl-prefix: "";
@import "../../vendor/kadet/keylighter/Styles/Html/keylighter";

17
assets/sass/_sidebar.scss Executable file
View File

@ -0,0 +1,17 @@
#sidebar {
margin-top: -2rem;
padding-top: 2rem;
.category-title {
margin-top: 2rem;
font-size: 1.25rem;
}
.nav-item.active {
border-left: map_get($theme-colors, 'primary') 3px solid;
a {
margin-left: -3px;
}
}
}

5
assets/sass/_variables.scss Executable file
View File

@ -0,0 +1,5 @@
$color-dark: #131313;
$color-accent: #FF9700;
$color-light: #DDDDDD;
$font-family-monospace: "JetBrains Mono Medium", "Fira Mono", monospace;

137
assets/sass/style.scss Executable file
View File

@ -0,0 +1,137 @@
@import "variables";
@import "bootstrap";
@import "../fonts/JetBrainsMono";
@import "keylighter";
@import "sidebar";
@import "docs";
$footer-height: 60px;
html {
font-size: 14px;
position: relative;
min-height: 100%;
}
body {
margin-bottom: $footer-height;
}
#content {
padding: 5rem 0;
img {
max-width: 100%;
}
}
#footer {
padding: 1rem;
line-height: 2rem;
background: $gray-100;
border-top: 1px solid $gray-300;
vertical-align: middle;
height: $footer-height;
position: absolute;
bottom: 0;
left: 0; right: 0;
span, a {
display: inline-block;
line-height: 2rem;
}
img {
display: inline-block;
margin-left: .3rem;
height: 2rem;
vertical-align: middle;
margin-top: -4px;
}
}
.timing {
height: 1.5rem;
.progress-bar {
text-overflow: ellipsis;
overflow: hidden;
}
}
.snippet {
> nav, > aside, > pre, > section {
margin-bottom: 1rem;
}
aside {
font-weight: bolder;
}
}
#try-form {
padding: 1rem 0;
color: $foreground;
background: $color-dark;
textarea, label, input {
font-family: $font-family-monospace;
}
textarea, input {
border-radius: 0;
border: none;
background: $background;
color: $foreground;
}
textarea {
padding: 1.5rem;
}
&.hidden {
display: none;
}
&.visible {
display: block;
}
}
.d-flex.dual {
justify-content: space-between;
}
.nav-item > a.slim {
padding-left: 0 !important;
}
#form-toggle {
&.visible svg {
transform: rotate(180deg);
}
svg {
transition: transform 300ms ease, color 100ms ease;
}
}
#language-selector {
> a.language {
margin-bottom: .5rem;
margin-right: .5rem;
display: none;
}
margin-bottom: -.5rem;
&.standalone > a.standalone { display: inline-block }
&.embeddable > a.embeddable { display: inline-block }
a.hidden { display: none !important; }
}

26
assets/ts/app.ts Normal file
View File

@ -0,0 +1,26 @@
import 'bootstrap'
import TryItForm from "./form"
import '@fortawesome/fontawesome-pro/css/all.min.css'
import '../sass/style.scss'
$('[data-toggle="tooltip"]').tooltip();
let $form = $('#try-form');
let $toggle = $('#form-toggle');
let form = new TryItForm($form, $toggle);
$toggle.click(() => {
form.toggle();
return false;
});
let $edit = $('#snippet-edit').click(() => {
let $snippet = $('#snippet');
form.source = $snippet.data('source');
form.language = $snippet.data('language');
form.show();
});

115
assets/ts/form.ts Executable file
View File

@ -0,0 +1,115 @@
export default class TryItForm {
private $form: JQuery;
private $toggle: JQuery;
private $picker: JQuery;
constructor($form: JQuery, $toggle: JQuery) {
this.$form = $form;
this.$picker = $form.find('.languages');
this.$picker.find('.language').click(e => this.pick(e));
this.$form.find('[name="language"]').on('input', e => this.update(e));
this.$toggle = $toggle;
}
public toggle() {
if (this.$form.hasClass('visible')) {
this.hide();
} else {
this.show();
}
}
public show() {
this.$form.slideDown({
complete: () => this.$form.addClass('visible')
});
this.$toggle
.addClass('visible')
.addClass('active');
return this;
}
public hide() {
this.$form.slideUp({
complete: () => this.$form.removeClass('visible')
});
this.$toggle
.removeClass('visible')
.removeClass('active');
return this;
}
get source() {
return this.$form.find('[name="source"]').val();
}
set source(value: string) {
this.$form.find('[name="source"]').val(value);
}
get language() {
return this.$form.find('[name="language"]').val();
}
set language(value: string) {
this.$form.find('[name="language"]').val(value);
}
public set(source: string, language: string) {
this.source = source;
this.language = language;
}
public submit() {
this.$form.find('form').submit();
}
private update(e: JQueryKeyEventObject) {
let $this = $(e.currentTarget);
let tokens = $this.val()
.split(/\b/)
.map(x => x.trim())
.filter(x => !!x);
let last: string|boolean = false;
if (tokens.length) {
last = tokens[tokens.length - 1];
if (last == '>') {
last = false;
}
}
let standalone = tokens.indexOf('>') == -1;
this.$picker
.toggleClass('embeddable', !standalone)
.toggleClass('standalone', standalone);
this.$picker.find('.language').each((index, elem) => {
let $elem = $(elem);
let show = !last || (typeof last == 'string' && $elem.text().indexOf(last) !== -1);
$elem.toggleClass('hidden', !show);
})
}
private pick(e: JQueryEventObject) {
let $this = $(e.currentTarget);
let $selector = this.$form.find('[name="language"]');
let input = <HTMLInputElement>$selector[0];
let value = input.value;
let start = input.selectionStart;
let end = input.selectionEnd;
while(start > 0 && value[start-1] != ' ' && start--) {}
while(end < value.length && value[end] != ' ' && end++) {}
input.value = value.substring(0, start) + $this.text().trim() + value.substring(end);
return false;
}
}

View File

@ -1,64 +1,72 @@
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.2.5",
"ext-ctype": "*",
"ext-iconv": "*",
"symfony/console": "5.0.*",
"symfony/dotenv": "5.0.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "5.0.*",
"symfony/orm-pack": "^1.0",
"symfony/twig-pack": "^1.0",
"symfony/yaml": "5.0.*"
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.2.5",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-json": "*",
"kadet/keylighter": "dev-master",
"league/commonmark": "^1.3",
"sensio/framework-extra-bundle": "^5.5",
"symfony/asset": "5.0.*",
"symfony/console": "5.0.*",
"symfony/dotenv": "5.0.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "5.0.*",
"symfony/orm-pack": "^1.0",
"symfony/profiler-pack": "^1.0",
"symfony/twig-pack": "^1.0",
"symfony/webpack-encore-bundle": "^1.7",
"symfony/yaml": "5.0.*",
"tightenco/collect": "^7.5"
},
"require-dev": {
},
"config": {
"preferred-install": {
"*": "dist"
},
"require-dev": {
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.0.*"
}
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.0.*"
}
}
}

490
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f5f287a8a8950e4398c1b309fe77101d",
"content-hash": "e2304bcfe75a51f30dd11d1699bab82a",
"packages": [
{
"name": "doctrine/annotations",
@ -1228,6 +1228,133 @@
],
"time": "2014-01-12T16:20:24+00:00"
},
{
"name": "kadet/keylighter",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/kadet1090/KeyLighter.git",
"reference": "f3f2804ba69766681f8df1887f8e7d58a5504642"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kadet1090/KeyLighter/zipball/f3f2804ba69766681f8df1887f8e7d58a5504642",
"reference": "f3f2804ba69766681f8df1887f8e7d58a5504642",
"shasum": ""
},
"require": {
"php": "^7.1.3"
},
"require-dev": {
"easybook/geshi": "v1.0.8.19",
"ext-json": "*",
"phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.5",
"symfony/console": "^4.4",
"symfony/var-exporter": "^4.4"
},
"suggest": {
"symfony/console": "Allows usage of keylighters CLI utility."
},
"bin": [
"bin/keylighter"
],
"type": "library",
"autoload": {
"psr-4": {
"Kadet\\Highlighter\\": "."
},
"files": [
"./functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kacper Donat",
"email": "contact@kadet.net"
}
],
"description": "Yet another syntax highlighter for PHP",
"time": "2020-04-09T07:40:39+00:00"
},
{
"name": "league/commonmark",
"version": "1.3.3",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "5a67afc2572ec6d430526cdc9c637ef124812389"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/5a67afc2572ec6d430526cdc9c637ef124812389",
"reference": "5a67afc2572ec6d430526cdc9c637ef124812389",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1"
},
"conflict": {
"scrutinizer/ocular": "1.7.*"
},
"require-dev": {
"cebe/markdown": "~1.0",
"commonmark/commonmark.js": "0.29.1",
"erusev/parsedown": "~1.0",
"ext-json": "*",
"github/gfm": "0.29.0",
"michelf/php-markdown": "~1.4",
"mikehaertl/php-shellcommand": "^1.4",
"phpstan/phpstan-shim": "^0.11.5",
"phpunit/phpunit": "^7.5",
"scrutinizer/ocular": "^1.5",
"symfony/finder": "^4.2"
},
"bin": [
"bin/commonmark"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"League\\CommonMark\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com",
"role": "Lead Developer"
}
],
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)",
"homepage": "https://commonmark.thephpleague.com",
"keywords": [
"commonmark",
"flavored",
"gfm",
"github",
"github-flavored",
"markdown",
"md",
"parser"
],
"time": "2020-04-05T16:01:48+00:00"
},
{
"name": "ocramius/package-versions",
"version": "1.5.1",
@ -1537,6 +1664,149 @@
],
"time": "2020-03-23T09:12:05+00:00"
},
{
"name": "sensio/framework-extra-bundle",
"version": "v5.5.4",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
"reference": "d0585d4825a87a5030ca8cd34adb4a17e1066c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/d0585d4825a87a5030ca8cd34adb4a17e1066c17",
"reference": "d0585d4825a87a5030ca8cd34adb4a17e1066c17",
"shasum": ""
},
"require": {
"doctrine/annotations": "^1.0",
"php": ">=7.1.3",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/framework-bundle": "^4.4|^5.0",
"symfony/http-kernel": "^4.4|^5.0"
},
"conflict": {
"doctrine/doctrine-cache-bundle": "<1.3.1"
},
"require-dev": {
"doctrine/doctrine-bundle": "^1.11|^2.0",
"doctrine/orm": "^2.5",
"nyholm/psr7": "^1.1",
"symfony/browser-kit": "^4.4|^5.0",
"symfony/dom-crawler": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",
"symfony/monolog-bridge": "^4.0|^5.0",
"symfony/monolog-bundle": "^3.2",
"symfony/phpunit-bridge": "^4.3.5|^5.0",
"symfony/psr-http-message-bridge": "^1.1",
"symfony/security-bundle": "^4.4|^5.0",
"symfony/twig-bundle": "^4.4|^5.0",
"symfony/yaml": "^4.4|^5.0",
"twig/twig": "^1.34|^2.4|^3.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "5.5.x-dev"
}
},
"autoload": {
"psr-4": {
"Sensio\\Bundle\\FrameworkExtraBundle\\": "src/"
},
"exclude-from-classmap": [
"/tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "This bundle provides a way to configure your controllers with annotations",
"keywords": [
"annotations",
"controllers"
],
"time": "2020-04-06T12:20:39+00:00"
},
{
"name": "symfony/asset",
"version": "v5.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset.git",
"reference": "8872144d5d9f28eec62857400bb437693ef4d082"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/asset/zipball/8872144d5d9f28eec62857400bb437693ef4d082",
"reference": "8872144d5d9f28eec62857400bb437693ef4d082",
"shasum": ""
},
"require": {
"php": "^7.2.5"
},
"require-dev": {
"symfony/http-foundation": "^4.4|^5.0",
"symfony/http-kernel": "^4.4|^5.0"
},
"suggest": {
"symfony/http-foundation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Asset\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Asset Component",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-27T16:56:45+00:00"
},
{
"name": "symfony/cache",
"version": "v5.0.7",
@ -3158,6 +3428,34 @@
],
"time": "2020-02-27T09:26:54+00:00"
},
{
"name": "symfony/profiler-pack",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/profiler-pack.git",
"reference": "99c4370632c2a59bb0444852f92140074ef02209"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/profiler-pack/zipball/99c4370632c2a59bb0444852f92140074ef02209",
"reference": "99c4370632c2a59bb0444852f92140074ef02209",
"shasum": ""
},
"require": {
"php": "^7.0",
"symfony/stopwatch": "*",
"symfony/twig-bundle": "*",
"symfony/web-profiler-bundle": "*"
},
"type": "symfony-pack",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A pack for the Symfony web profiler",
"time": "2018-12-10T12:11:44+00:00"
},
{
"name": "symfony/routing",
"version": "v5.0.7",
@ -3822,6 +4120,139 @@
],
"time": "2020-03-27T16:56:45+00:00"
},
{
"name": "symfony/web-profiler-bundle",
"version": "v5.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/web-profiler-bundle.git",
"reference": "635bf7fe86b67b0d3903a3013709fe028ac43b59"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/635bf7fe86b67b0d3903a3013709fe028ac43b59",
"reference": "635bf7fe86b67b0d3903a3013709fe028ac43b59",
"shasum": ""
},
"require": {
"php": "^7.2.5",
"symfony/config": "^4.4|^5.0",
"symfony/framework-bundle": "^4.4|^5.0",
"symfony/http-kernel": "^4.4|^5.0",
"symfony/routing": "^4.4|^5.0",
"symfony/twig-bundle": "^4.4|^5.0",
"twig/twig": "^2.10|^3.0"
},
"conflict": {
"symfony/form": "<4.4",
"symfony/messenger": "<4.4"
},
"require-dev": {
"symfony/browser-kit": "^4.4|^5.0",
"symfony/console": "^4.4|^5.0",
"symfony/css-selector": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/stopwatch": "^4.4|^5.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Bundle\\WebProfilerBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony WebProfilerBundle",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-27T16:56:45+00:00"
},
{
"name": "symfony/webpack-encore-bundle",
"version": "v1.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/webpack-encore-bundle.git",
"reference": "5c0f659eceae87271cce54bbdfb05ed8ec9007bd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/5c0f659eceae87271cce54bbdfb05ed8ec9007bd",
"reference": "5c0f659eceae87271cce54bbdfb05ed8ec9007bd",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/asset": "^3.4 || ^4.0 || ^5.0",
"symfony/config": "^3.4 || ^4.0 || ^5.0",
"symfony/dependency-injection": "^3.4 || ^4.0 || ^5.0",
"symfony/http-kernel": "^3.4 || ^4.0 || ^5.0",
"symfony/service-contracts": "^1.0 || ^2.0"
},
"require-dev": {
"symfony/framework-bundle": "^3.4 || ^4.0 || ^5.0",
"symfony/phpunit-bridge": "^4.3.5 || ^5.0",
"symfony/twig-bundle": "^3.4 || ^4.0 || ^5.0",
"symfony/web-link": "^3.4 || ^4.0 || ^5.0"
},
"type": "symfony-bundle",
"extra": {
"thanks": {
"name": "symfony/webpack-encore",
"url": "https://github.com/symfony/webpack-encore"
}
},
"autoload": {
"psr-4": {
"Symfony\\WebpackEncoreBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Integration with your Symfony app & Webpack Encore!",
"time": "2020-01-31T15:31:59+00:00"
},
{
"name": "symfony/yaml",
"version": "v5.0.7",
@ -3895,6 +4326,56 @@
],
"time": "2020-03-30T11:42:42+00:00"
},
{
"name": "tightenco/collect",
"version": "v7.5.2",
"source": {
"type": "git",
"url": "https://github.com/tightenco/collect.git",
"reference": "1f5f7a36657a126fa035c7cfaafd738391690967"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tightenco/collect/zipball/1f5f7a36657a126fa035c7cfaafd738391690967",
"reference": "1f5f7a36657a126fa035c7cfaafd738391690967",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/var-dumper": "^3.4 || ^4.0 || ^5.0"
},
"require-dev": {
"mockery/mockery": "^1.0",
"nesbot/carbon": "^2.23.0",
"phpunit/phpunit": "^7.0"
},
"type": "library",
"autoload": {
"files": [
"src/Collect/Support/helpers.php",
"src/Collect/Support/alias.php"
],
"psr-4": {
"Tightenco\\Collect\\": "src/Collect"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"description": "Collect - Illuminate Collections as a separate package.",
"keywords": [
"collection",
"laravel"
],
"time": "2020-04-08T17:29:27+00:00"
},
{
"name": "twig/extra-bundle",
"version": "v3.0.3",
@ -4133,13 +4614,16 @@
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"kadet/keylighter": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.2.5",
"ext-ctype": "*",
"ext-iconv": "*"
"ext-iconv": "*",
"ext-json": "*"
},
"platform-dev": [],
"plugin-api-version": "1.1.0"

View File

@ -6,4 +6,7 @@ return [
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
];

View File

@ -0,0 +1,3 @@
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'

View File

@ -0,0 +1,6 @@
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler: { only_exceptions: false }

View File

@ -10,7 +10,7 @@ framework:
cookie_secure: auto
cookie_samesite: lax
#esi: true
#fragments: true
esi: true
fragments: true
php_errors:
log: true

View File

@ -0,0 +1,4 @@
#webpack_encore:
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# Available in version 1.2
#cache: true

View File

@ -0,0 +1,3 @@
sensio_framework_extra:
router:
annotations: false

View File

@ -0,0 +1,6 @@
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@ -0,0 +1,2 @@
#webpack_encore:
# strict_mode: false

View File

@ -1,2 +1,5 @@
twig:
default_path: '%kernel.project_dir%/templates'
default_path: '%kernel.project_dir%/templates'
globals:
versioner: '@twig.versioner'
keylighter: '@twig.keylighter'

View File

@ -0,0 +1,25 @@
webpack_encore:
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
output_path: '%kernel.project_dir%/public/build'
# If multiple builds are defined (as shown below), you can disable the default build:
# output_path: false
# if using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
# preload all rendered script and link tags automatically via the http2 Link header
# preload: true
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
# strict_mode: false
# if you have multiple builds:
# builds:
# pass "frontend" as the 3rg arg to the Twig functions
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
# frontend: '%kernel.project_dir%/public/frontend/build'
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# Put in config/packages/prod/webpack_encore.yaml
# cache: true

View File

@ -0,0 +1,7 @@
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View File

@ -1,27 +1,64 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
keylighter_dir: '%kernel.project_dir%/var/keylighter/'
keylighter_temp_dir: '%kernel.cache_dir%/keylighter/'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../src/{Entity,Repository,Tests,Listener,Migrations}'
lazy: true
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Service\:
resource: '../src/Service/*'
public: true
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
App\Controller\:
resource: '../src/Controller'
public: true
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
App\Command\:
resource: '../src/Command'
public: true
tags: ['console.command']
App\Listener\KeyLighterVersionListener:
tags:
- { name: kernel.event_listener, event: kernel.request }
- { name: kernel.event_listener, event: kernel.response }
Kadet\Highlighter\KeyLighter:
class: Kadet\Highlighter\KeyLighter
factory: 'Kadet\Highlighter\KeyLighter::get'
League\CommonMark\Environment:
factory: ['League\CommonMark\Environment', 'createGFMEnvironment']
calls:
- ['addExtension', ['@App\CommonMark\KeylighterExtension']]
League\CommonMark\EnvironmentInterface: '@League\CommonMark\Environment'
League\CommonMark\DocParser: ~
League\CommonMark\DocParserInterface: '@League\CommonMark\DocParser'
League\CommonMark\HtmlRenderer: ~
twig.versioner: '@App\Service\KeyLighterVersioner'
twig.keylighter: '@App\Twig\KeyLighterTwigAccess'

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"devDependencies": {
"@symfony/webpack-encore": "^0.28.2",
"@types/bootstrap": "^3.3.32",
"@types/jquery": "^2.0.41",
"bootstrap": "4.0.0-beta",
"core-js": "^3.0.0",
"firacode": "^1.205.0",
"jquery": "^3.2.1",
"node-sass": "^4.13.1",
"popper.js": "^1.12.5",
"regenerator-runtime": "^0.13.2",
"sass-loader": "7.0.3",
"ts-loader": "^5.3.0",
"typescript": "^3.8.3",
"webpack-notifier": "^1.6.0",
"@fortawesome/fontawesome-pro": "^5.13.0"
},
"license": "UNLICENSED",
"private": true,
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Command;
use App\Service\KeyLighterVersioner;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class KeyLighterBenchmark extends Command
{
/** @var \App\Service\KeyLighterVersioner */
private $versioner;
/**
* KeyLighterUpdate constructor.
*
* @param \App\Service\KeyLighterVersioner $versioner
*/
public function __construct(KeyLighterVersioner $versioner)
{
$this->versioner = $versioner;
parent::__construct('keylighter:benchmark');
}
protected function configure()
{
$this->addArgument('version', InputArgument::REQUIRED, 'keylighter version to benchmark');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$directory = $this->versioner->getDirectory($input->getArgument('version'));
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Command;
use App\Service\KeyLighterVersioner;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class KeyLighterUpdate extends Command
{
/** @var \App\Service\KeyLighterVersioner */
private $versioner;
/**
* KeyLighterUpdate constructor.
*
* @param \App\Service\KeyLighterVersioner $versioner
*/
public function __construct(KeyLighterVersioner $versioner)
{
$this->versioner = $versioner;
parent::__construct('keylighter:update');
}
protected function configure()
{
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->versioner->updateAll();
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\CommonMark;
use Kadet\Highlighter\KeyLighter;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
class KeylighterCodeRenderer implements BlockRendererInterface
{
private $keylighter;
private $formatter;
public function __construct(KeyLighter $keylighter)
{
$this->keylighter = $keylighter;
$this->formatter = $keylighter->getFormatter('html');
}
/**
* @inheritDoc
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
$language = $this->keylighter->getLanguage($block->getInfo());
$text = $block->getStringContent();
return new HtmlElement('pre', ['class' => 'keylighter'], @$this->keylighter->highlight($text, $language, $this->formatter));
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\CommonMark;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\FencedCode;
use League\CommonMark\Block\Element\IndentedCode;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Extension\Table\Table;
use League\CommonMark\Extension\Table\TableRenderer;
use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class KeylighterExtension implements ExtensionInterface, ServiceSubscriberInterface
{
private $provider;
public function __construct(ServiceProviderInterface $provider)
{
$this->provider = $provider;
}
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addBlockRenderer(FencedCode::class, $this->provider->get(KeylighterCodeRenderer::class), 150);
$environment->addBlockRenderer(IndentedCode::class, $this->provider->get(KeylighterCodeRenderer::class), 150);
$environment->addBlockRenderer(Table::class, new class implements BlockRendererInterface {
private $decorated;
public function __construct()
{
$this->decorated = new TableRenderer();
}
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
$element = $this->decorated->render($block, $htmlRenderer, $inTightList);
$element->setAttribute('class', 'table');
return $element;
}
});
}
/**
* @inheritDoc
*/
public static function getSubscribedServices()
{
return [
KeylighterCodeRenderer::class
];
}
}

0
src/Migrations/.gitignore → src/Consumer/.gitignore vendored Normal file → Executable file
View File

View File

@ -0,0 +1,205 @@
<?php
namespace App\Consumer;
use App\Exception\CommandExecuteException;
use FilesystemIterator;
use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface;
use PhpAmqpLib\Message\AMQPMessage;
use Psr\Log\LoggerInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
class UpdateKeylighterConsumer implements ConsumerInterface
{
/** @var \App\Service\KeyLighterVersioner */
private $versioner;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
private $composer;
private $filesystem;
private $temp;
private $installed;
private $box;
/**
* UpdateKeylighterConsumer constructor.
*
* @param \Psr\Log\LoggerInterface $logger
* @param \Symfony\Component\Filesystem\Filesystem $filesystem
* @param $composer
* @param $box
*/
public function __construct(LoggerInterface $logger, Filesystem $filesystem, $composer, $box)
{
$this->logger = $logger;
$this->composer = $composer;
$this->box = $box;
$this->filesystem = $filesystem;
}
/**
* @return mixed
*/
public function getTempFile()
{
return $this->filesystem->tempnam($this->temp, 'keylighter');
}
public function getTempDir()
{
$path = $this->temp.DIRECTORY_SEPARATOR.uniqid('keylighter');
$this->filesystem->mkdir($path, 0755);
return realpath($path);
}
/**
* @param mixed $temp
*/
public function setTemp($temp)
{
$this->temp = $temp;
$this->filesystem->mkdir($this->temp, 0755);
}
public function setInstalledFile(string $path)
{
$this->installed = $path;
}
/**
* @param AMQPMessage $msg The message
*
* @return mixed false to reject and requeue, any other value to acknowledge
*/
public function execute(AMQPMessage $msg)
{
list($destination, $revision) = unserialize($msg->getBody());
$this->logger->info(sprintf('Downloading KeyLighter revision %s to %s', $revision['version'], $destination));
return $this->download($revision, $destination);
}
private function download($revision, $destination)
{
$temp = $this->getTempDir();
$archive = new \ZipArchive();
$archive->open($this->downloadArchive($revision));
$archive->extractTo($temp);
$archive->close();
$this->move($temp, $destination);
$cwd = getcwd();
chdir($destination);
try {
$this->runCommands([ $this->composer.' install -n --no-ansi --quiet' ]);
if (file_exists('./box.json.dist')) {
$this->runCommands([ $this->box.' build -n --no-ansi']);
$this->logger->info(sprintf('keylighter.phar build OK'));
}
} catch (CommandExecuteException $exception) {
$this->logger->error($exception->getMessage(), [
'code' => $exception->getCode(),
'output' => $exception->getOutput()
]);
return false;
} finally {
chdir($cwd);
}
$this->logger->info(sprintf('Download OK'));
$this->addInstalledEntry($revision['version'], $revision['dist']['reference']);
return true;
}
private function runCommands(array $commands)
{
$commands = array_map(function($command) {
return str_replace('@php', PHP_BINARY, $command);
}, $commands);
$commands = implode('; ', $commands);
$this->logger->debug('Executing: '.$commands);
exec($commands, $output, $code);
if ($code !== 0) {
throw new CommandExecuteException("Command `$commands` returned non-zero exit code.", implode("\n", $output), $code);
}
return true;
}
private function move($temp, $final)
{
try {
/** @var \SplFileInfo[] $files */
$files = array_values(iterator_to_array(new FilesystemIterator($temp, FilesystemIterator::SKIP_DOTS)));
// really nasty hack
if (count($files) === 1 && $files[0]->isDir()) {
$dir = $files[0]->getPathName();
} else {
$dir = $temp;
}
if ($this->filesystem->exists($final)) {
$this->filesystem->remove($final);
}
$this->filesystem->rename($dir, $final, true);
return true;
} catch (IOException $exception) {
return false;
}
}
private function downloadArchive($revision)
{
try {
$temp = $this->getTempFile();
$result = fopen($temp, 'w');
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $revision['dist']['url']);
curl_setopt($curl, CURLOPT_FILE, $result);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_USERAGENT, "keylighter.kadet.net/1.0");
if (!curl_exec($curl)) {
throw new \RuntimeException(curl_error($curl), curl_errno($curl));
}
if (($code = curl_getinfo($curl, CURLINFO_HTTP_CODE)) !== 200) {
throw new \RuntimeException("Response code was $code.");
}
return $temp;
} finally {
curl_close($curl);
fclose($result);
}
}
private function addInstalledEntry($version, $checksum)
{
$entries = file_exists($this->installed) ? json_decode(file_get_contents($this->installed), true) : [];
$entries[$version] = $checksum;
file_put_contents($this->installed, json_encode($entries, true), LOCK_EX);
}
}

0
src/Controller/.gitignore vendored Normal file → Executable file
View File

View File

@ -0,0 +1,25 @@
<?php
namespace App\Controller;
use App\Entity\Snippet;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class DemoController extends AbstractController
{
/**
* @Route("/highlight", name="demo_highlight", methods={"POST"})
*/
public function highlightAction(Request $request)
{
$snippet = new Snippet();
$snippet->setLanguage($request->get('language'));
$snippet->setCode($request->get('source'));
return $this->forward(sprintf('%s::showAction', SnippetController::class), ['snippet' => $snippet]);
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Controller;
use App\Entity\Document;
use App\Service\DocumentResolver;
use Kadet\Highlighter\KeyLighter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class DocumentationController
*
* @package App\Controller
* @Route("/docs")
*/
class DocumentationController extends AbstractController
{
private const DEFAULT_FILE = null;
private $resolver;
/**
* HomeController constructor.
*
* @param DocumentResolver $resolver
*/
public function __construct(DocumentResolver $resolver)
{
$this->resolver = $resolver;
}
public function sidebarAction($path = null)
{
$category = function (Document $document) {
return $document->getMetadata()['category'];
};
$title = function (Document $document) {
return $document->getMetadata()['title'];
};
/** @var \App\Entity\Document[][] $result */
$result = collect($this->resolver->fetch('/Docs/'))
->sortBy($title)->sortBy($category)
->groupBy($category);
return $this->render('docs/sidebar.html.twig', [
'categories' => $result,
'path' => $path
]);
}
/**
* @Route("/{path}", name="documentation_show", requirements={"path": ".+"})
*/
public function showAction($path = self::DEFAULT_FILE)
{
try {
if ($path == self::DEFAULT_FILE) {
$path = $this->getDefaultFile();
}
$document = $this->resolver->getDocument('/Docs/'.$path);
$meta = $document->getMetadata();
$prev = false;
$next = false;
if (isset($meta['next'])) {
$next = $this->resolver->getDocument('/Docs/'.$meta['next']);
}
if (isset($meta['previous'])) {
$prev = $this->resolver->getDocument('/Docs/'.$meta['previous']);
}
return $this->render("docs/document.html.twig", compact('document', 'path', 'prev', 'next'));
} catch (FileNotFoundException $exception) {
if ($path == $this->getDefaultFile()) {
throw new NotFoundHttpException("Documentation starting point for that version is invalid.");
}
return $this->redirectToRoute('documentation_show');
}
}
private function getDefaultFile()
{
$version = KeyLighter::VERSION;
$version = str_replace('-', '.0-', $version); // keylighter uses major.minor-
if (version_compare($version, '0.9.0-dev', '>=')) {
return '1-installation';
} elseif (version_compare($version, '0.8.0', '>=')) {
return 'usage';
}
throw new NotFoundHttpException("No documentation was found for that version :(");
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Controller;
use App\Service\DocumentResolver;
use App\Service\KeyLighterVersioner;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
/** @var DocumentResolver */
private $resolver;
/** @var \App\Service\KeyLighterVersioner */
private $versioner;
/**
* HomeController constructor.
*
* @param DocumentResolver $resolver
*/
public function __construct(DocumentResolver $resolver, KeyLighterVersioner $versioner)
{
$this->resolver = $resolver;
$this->versioner = $versioner;
}
/**
* @Route("/", name="homepage")
*/
public function indexAction()
{
$document = $this->resolver->getDocument('README');
return $this->render('homepage.html.twig', compact('document'));
}
/**
* @Route("/download/keylighter.phar")
* @Route("/download/{version}/keylighter.phar")
*/
public function downloadPharAction($version = null)
{
$directory = $this->versioner->getCurrentDir();
if ($version) {
$directory = $this->versioner->getDirectory($version);
}
$directory .= DIRECTORY_SEPARATOR;
return $this->file($directory."keylighter.phar");
}
/**
* @Route("/changelog", name="changelog")
*/
public function changelogAction()
{
$document = $this->resolver->getDocument('changelog');
return $this->render('document.html.twig', [
'document' => $document,
'current' => 'changelog'
]);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Controller;
use App\Service\KeyLighterVersioner;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class WebHookController
*
* @package App\Controller
* @Route("/_webhook")
*/
class WebHookController extends AbstractController
{
/**
* @var \App\Service\KeyLighterVersioner
*/
private $versioner;
/**
* WebHookController constructor.
*
* @param \App\Service\KeyLighterVersioner $versioner
*/
public function __construct(KeyLighterVersioner $versioner)
{
$this->versioner = $versioner;
}
/**
* @Route("/travis")
*/
public function travisTestsPassedAction(Request $request)
{
$this->versioner->updateAll();
return new JsonResponse([
"status" => "OK"
], 200);
}
}

0
src/Entity/.gitignore vendored Normal file → Executable file
View File

63
src/Entity/Document.php Executable file
View File

@ -0,0 +1,63 @@
<?php
namespace App\Entity;
class Document
{
private $content;
private $metadata = [];
/**
* Document constructor.
*
* @param string $content
* @param array $metadata
*/
public function __construct(string $content, array $metadata)
{
$this->setContent($content);
$this->setMetadata($metadata);
}
/**
* @return mixed
*/
public function getContent()
{
return $this->content;
}
/**
* @param mixed $content
*
* @return $this
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* @return mixed
*/
public function getMetadata(): array
{
return $this->metadata;
}
/**
* @param mixed $metadata
*
* @return $this
*/
public function setMetadata(array $metadata)
{
$this->metadata = $metadata;
return $this;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Exception;
class CommandExecuteException extends \RuntimeException
{
private $output = "";
public function __construct(string $message = "", string $output = "", int $code = 0, \Throwable $previous = null)
{
$this->output = $output;
parent::__construct($message, $code, $previous);
}
/**
* @return string
*/
public function getOutput(): string
{
return $this->output;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Listener;
use App\Service\KeyLighterVersioner;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
class KeyLighterVersionListener
{
private $versioner;
/**
* KeyLighterVersionListener constructor.
*
* @param $versioner
*/
public function __construct(KeyLighterVersioner $versioner)
{
$this->versioner = $versioner;
}
public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$cookie = $event->getRequest()->cookies->get('keylighter_version', 'dev-master');
$version = $event->getRequest()->query->get('keylighter', $cookie);
$this->versioner->load($version);
}
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if ($event->getRequest()->query->has('keylighter')) {
$cookie = new Cookie('keylighter_version', $this->versioner->getCurrent());
$event->getResponse()->headers->setCookie($cookie);
}
}
}

View File

View File

@ -0,0 +1,73 @@
<?php
namespace App\Service;
use App\Entity\Document;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Yaml\Yaml;
class DocumentResolver
{
private $base;
/**
* DocumentResolver constructor.
*
* @param \App\Service\KeyLighterVersioner $versioner
*/
public function __construct(KeyLighterVersioner $versioner)
{
$this->base = $versioner->getCurrentDir();
}
public function fetch($path)
{
$path = $this->buildPath($path);
$finder = new Finder();
$finder->name("*.md");
foreach ($finder->files()->in($path)->getIterator() as $file) {
$document = $this->getDocument($file->getPathname(), true);
$metadata = array_merge([
"title" => str_replace('-', ' ', $file->getBasename('.md')),
"category" => basename($file->getPath()),
"path" => str_replace(['.md', DIRECTORY_SEPARATOR], [null, '/'], substr($file->getPathname(), strlen($path)+1))
], $document->getMetadata());
$document->setMetadata($metadata);
yield $file->getFilename() => $document;
}
}
public function getDocument($path, $absolute = false)
{
$path = $absolute ? $path : $this->buildPath(trim($path, '/').'.md');
if (!file_exists($path)) {
throw new FileNotFoundException("File '$path' was not found.");
}
list($content, $meta) = $this->split(file_get_contents($path));
return new Document($content, $meta);
}
private function split(string $content): array
{
if(!preg_match('/^-{3,}\h*\R(?P<metadata>.*?)\R-{3,}\h*\R/sm', $content, $matches)) {
return [ $content, [] ];
}
$metadata = $matches['metadata'];
return [ trim(substr($content, strlen($matches[0]))), Yaml::parse($metadata) ];
}
private function buildPath($path)
{
return realpath($this->base.DIRECTORY_SEPARATOR.$path);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Service;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Filesystem\Filesystem;
class KeyLighterVersioner
{
const URL = 'https://packagist.org/p/kadet/keylighter.json';
private $dir = '';
private $current;
private $filesystem;
private $parameters;
public function __construct(Filesystem $filesystem, ParameterBagInterface $parameters)
{
$this->filesystem = $filesystem;
$this->parameters = $parameters;
}
public function getAvailableVersions()
{
$info = $this->getInstalledInfo();
return array_keys($info);
}
public function getInstalledInfo()
{
$file = $this->getDirectory().'/installed.json';
if (!file_exists($file)) {
return [];
}
return json_decode(file_get_contents($file), true);
}
public function updateAll()
{
$info = $this->getPackageInfo();
$installed = $this->getInstalledInfo();
foreach ($info as $version => $revision) {
if (array_key_exists($version, $installed) && $installed[$version] == $revision['dist']['reference']) {
continue;
}
// fixme: add package to download queue
}
}
private function getPackageInfo()
{
return json_decode(file_get_contents(self::URL), true)['packages']['kadet/keylighter'];
}
public function getDirectory($version = null): string
{
return $this->ensureDirectoryExists($this->parameters->get('keylighter_dir').DIRECTORY_SEPARATOR.$version);
}
private function ensureDirectoryExists($directory)
{
$this->filesystem->mkdir($directory, 0755);
return realpath($directory);
}
public function load($version)
{
if(!in_array($version, $this->getAvailableVersions())) {
throw new \RuntimeException("'$version' is not an valid KeyLighter version, available are: ".implode(', ', $this->getAvailableVersions()));
}
if (!file_exists($this->getDirectory($version).'/vendor/autoload.php')) {
throw new \RuntimeException("'$version' has no autoload file generated, maybe it is not installed correctly?");
}
require $this->getDirectory($version).'/vendor/autoload.php';
$this->current = $version;
}
public function getCurrent()
{
return $this->current;
}
public function getCurrentDir()
{
return $this->getDirectory($this->current);
}
}

27
src/Twig/HelpersExtension.php Executable file
View File

@ -0,0 +1,27 @@
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class HelpersExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('fa', function($icon, $pack = 'regular') {
$pack = [
'regular' => 'far',
'solid' => 'fas',
'brand' => 'fab',
'light' => 'fal'
][$pack] ?? 'fa';
return "$pack fa-$icon";
})
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Twig;
use Kadet\Highlighter\Formatter\HtmlFormatter;
use Kadet\Highlighter\KeyLighter;
use Kadet\Highlighter\Language\Language;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use function Kadet\Highlighter\highlight;
class KeyLighterExtension extends AbstractExtension
{
public function getFilters()
{
$formatter = new HtmlFormatter(['prefix' => 'kl-']);
return [
new TwigFilter('highlight', function($source, $language) use ($formatter) {
if (!$language instanceof Language) {
$language = KeyLighter::get()->getLanguage($language);
}
return highlight($source, $language, $formatter);
}, ['pre_escape' => 'html', 'is_safe' => ['html']])
];
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Twig;
use App\Service\KeyLighterVersioner;
use Kadet\Highlighter\KeyLighter;
use Tightenco\Collect\Support\Collection;
class KeyLighterTwigAccess
{
private $keylighter;
private $versioner;
public function __construct(KeyLighter $keylighter, KeyLighterVersioner $versioner)
{
$this->keylighter = $keylighter;
$this->versioner = $versioner;
}
public function getLanguages()
{
$languages = require $this->versioner->getCurrentDir().'/Config/metadata.php';
$languages = collect($languages);
$languages = $languages->reduce(function (Collection $collection, $language) {
return $collection->merge(array_fill_keys($language['name'], [
'class' => $language[0],
'standalone' => $language['standalone'],
'embeddable' => $language['injectable']
]));
}, collect());
return $languages;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Twig;
use League\CommonMark\CommonMarkConverter;
use League\CommonMark\DocParserInterface;
use League\CommonMark\HtmlRenderer;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class MarkdownTwigExtension extends AbstractExtension
{
private $parser;
private $renderer;
public function __construct(DocParserInterface $parser, HtmlRenderer $renderer)
{
$this->parser = $parser;
$this->renderer = $renderer;
}
public function getFilters()
{
return [
new TwigFilter('markdown', function ($text) {
$block = $this->parser->parse($text);
return $this->renderer->renderBlock($block);
}, ['is_safe' => ['html']])
];
}
}

View File

@ -78,6 +78,12 @@
"jdorn/sql-formatter": {
"version": "v1.2.17"
},
"kadet/keylighter": {
"version": "dev-master"
},
"league/commonmark": {
"version": "1.3.3"
},
"ocramius/package-versions": {
"version": "1.5.1"
},
@ -99,6 +105,21 @@
"psr/log": {
"version": "1.1.3"
},
"sensio/framework-extra-bundle": {
"version": "5.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "5.2",
"ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b"
},
"files": [
"config/packages/sensio_framework_extra.yaml"
]
},
"symfony/asset": {
"version": "v5.0.7"
},
"symfony/cache": {
"version": "v5.0.7"
},
@ -198,6 +219,9 @@
"symfony/polyfill-php73": {
"version": "v1.15.0"
},
"symfony/profiler-pack": {
"version": "v1.0.4"
},
"symfony/routing": {
"version": "4.2",
"recipe": {
@ -247,9 +271,45 @@
"symfony/var-exporter": {
"version": "v5.0.7"
},
"symfony/web-profiler-bundle": {
"version": "3.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "3.3",
"ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6"
},
"files": [
"config/packages/dev/web_profiler.yaml",
"config/packages/test/web_profiler.yaml",
"config/routes/dev/web_profiler.yaml"
]
},
"symfony/webpack-encore-bundle": {
"version": "1.6",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "master",
"version": "1.6",
"ref": "69e1d805ad95964088bd510c05995e87dc391564"
},
"files": [
"assets/css/app.css",
"assets/js/app.js",
"config/packages/assets.yaml",
"config/packages/prod/webpack_encore.yaml",
"config/packages/test/webpack_encore.yaml",
"config/packages/webpack_encore.yaml",
"package.json",
"webpack.config.js"
]
},
"symfony/yaml": {
"version": "v5.0.7"
},
"tightenco/collect": {
"version": "v7.5.2"
},
"twig/extra-bundle": {
"version": "v3.0.3"
},

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>

33
templates/demo/_form.html.twig Executable file
View File

@ -0,0 +1,33 @@
<form action="{{ url('demo_highlight') }}" method="post" class="try-form">
<div class="form-group">
<label for="form_source"><i class="{{ fa('file-code') }}"></i> try it</label>
<textarea name="source" class="form-control" id="form_source" rows="15" required></textarea>
</div>
<div class="form-group row">
<label for="form_language" class="col-md-2 col-form-label"><i class="{{ fa('lightbulb') }}"></i> language</label>
<div class="col-md-10">
<input type="text" name="language" id="form_language" class="form-control" required/>
</div>
</div>
<div class="form-group">
<div id="language-selector" class="standalone languages">
{% apply spaceless %}
{% for alias, language in keylighter.languages %}
<a href="#" class="btn btn-sm btn-outline-primary language {{ language.embeddable ? 'embeddable' }} {{ language.standalone ? 'standalone' }}">
{{ alias }}
</a>
{% endfor %}
{% endapply %}
</div>
</div>
<div class="d-flex flex-row-reverse">
<div class="form-group">
<button type="reset" class="btn btn-outline-danger">
<i class="{{ fa('trash') }}"></i> reset
</button>
<button type="submit" class="btn btn-outline-primary">
<i class="{{ fa('code') }}"></i> highlight
</button>
</div>
</div>
</form>

View File

@ -0,0 +1,110 @@
{% extends 'keylighter.html.twig' %}
{% set current = 'about' %}
{% import _self as macros %}
{% block content %}
<div class="snippet">
<div class="d-flex dual">
<div class="form-group">
<a href="#" class="btn btn-outline-primary" id="snippet-edit">
<i class="{{ fa('pen') }}"></i> edit
</a>
</div>
<div class="form-group">
{% if snippet.id is empty %}
<a href="#" class="btn btn-primary" id="snippet-save" data-toggle="modal"
data-target="#save-modal">
<i class="{{ fa('share') }}"></i> share
</a>
{% endif %}
<a href="#" class="btn btn-outline-danger">
<i class="{{ fa('github', 'brand') }}"></i> open issue
</a>
</div>
</div>
<pre class="keylighter" id="snippet" data-language="{{ snippet.language }}"
data-source="{{ snippet.code }}">{{ source|raw }}</pre>
<aside>
<span class="entry">
<span class="label"><i class="{{ fa('code') }}"></i></span>
{{ snippet.language }}
</span>
<span class="entry">
<span class="label"><i class="{{ fa('clock') }}"></i></span>
{{ (all*1000)|number_format(3) }}ms
</span>
<span class="entry">
<span class="label"><i class="{{ fa('cubes', 'solid') }}"></i></span>
{{ snippet.size|number_format }} bytes
</span>
<span class="entry float-right">
{% if snippet.showcase %}
<strong class="text-success"><i class="{{ fa('lightbulb') }}"></i> showcase</strong>
{% else %}
<strong class="text-muted"><i class="{{ fa('lock-alt') }}"></i> hidden</strong>
{% endif %}
</span>
</aside>
<section id="timing">
<div class="progress timing">
{{ macros.time(timing.parsing, all, 'info') }}
{{ macros.time(timing.tokenization, all, 'primary') }}
{{ macros.time(timing.formatting, all, 'secondary') }}
</div>
</section>
</div>
{% endblock %}
{% block modals %}
<div class="fade modal" tabindex="-1" role="dialog" id="save-modal">
<div class="modal-dialog modal-lg" role="document">
<form action="{{ url('snippet_save') }}" method="post">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Save your snippet</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="save-title">Title:</label>
<input type="text" class="form-control" id="save-title" name="title"/>
<small class="form-text text-muted">Title is required only if you want to share snippet in showcase.</small>
</div>
<div class="form-group">
<input type="hidden" value="{{ snippet.code }}" name="code"/>
<input type="hidden" value="{{ snippet.language }}" name="language"/>
<pre class="keylighter">{{ source|raw }}</pre>
<aside>
<span class="entry">
<span class="label"><i class="{{ fa('code') }}"></i></span>
{{ snippet.language }}
</span>
</aside>
</div>
<div class="form-group">
<label><input type="checkbox" name="showcase"> show that snippet in showcase gallery</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">cancel</button>
<button type="submit" class="btn btn-outline-primary"><i class="{{ fa('save') }}"></i> save</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% macro time(time, all, class) %}
{% set milliseconds = time * 1000 %}
<div class="progress-bar bg-{{ class }} has-tooltip" role="progressbar" style="width: {{ (time / all) * 100 }}%"
aria-valuenow="{{ milliseconds|number_format(2) }}" aria-valuemin="0"
aria-valuemax="{{ (all * 1000)|number_format(2) }}"
title="{{ milliseconds|number_format(2) }}ms" data-placement="bottom" data-toggle="tooltip"
>
</div>
{% endmacro %}

View File

@ -0,0 +1,27 @@
{% extends 'keylighter.html.twig' %}
{% set showForm = true %}
{% set current = 'showcase' %}
{% import _self as macros %}
{% block content %}
{% for snippet in snippets %}
<div class="snippet">
<h1>
<a href="{{ url('snippet_show', { snippet: snippet.id }) }}">{{ snippet.title|default('untitled') }}</a>
</h1>
<pre class="keylighter">{{ snippet.code|highlight(snippet.language) }}</pre>
<aside>
<span class="entry">
<span class="label"><i class="{{ fa('code') }}"></i></span>
{{ snippet.language }}
</span>
<span class="float-right entry">
<span class="label"><i class="{{ fa('hashtag') }}"></i></span>
<a href="{{ url('snippet_show', { snippet: snippet.id }) }}">{{ snippet.id }}</a>
</span>
</aside>
</div>
{% endfor %}
{% endblock %}

View File

@ -0,0 +1,36 @@
{% extends 'keylighter.html.twig' %}
{% set current = 'docs' %}
{% import 'macros.twig' as helpers %}
{% block content %}
<div class="row">
<div class="col-md-3">
<div id="sidebar" class="sticky-top">
<h2>Documentation</h2>
{{ render_esi(controller("App\\Controller\\DocumentationController:sidebarAction", { path: path })) }}
</div>
</div>
<div class="col-md-9">
<nav class="d-flex justify-content-end">
<a href="https://github.com/kadet1090/KeyLighter/edit/master/Docs/{{ path }}.md" class="btn btn-outline-primary">
<i class="{{ fa('github', 'brand') }}"></i> edit on github
</a>
</nav>
{{ document.content|markdown }}
<nav class="d-flex chapter--navigation">
{% if prev %}
<a href="{{ document.metadata['previous'] }}" class="btn btn-outline-primary nav--prev">
<i class="far fa-chevron-left"></i> {{ helpers.document_link(prev) }}
</a>
{% endif %}
{% if next %}
<a href="{{ document.metadata['next'] }}" class="btn btn-outline-primary nav--next">
{{ helpers.document_link(next) }} <i class="far fa-chevron-right"></i>
</a>
{% endif %}
</nav>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% import "macros.twig" as helpers %}
{% for name, documents in categories %}
{% if name|lower != 'docs' %}
<h3 class="category-title">{{ name|title }}</h3>
{% endif %}
<ul class="nav flex-column">
{% for document in documents %}
{{ helpers.menu_entry(
url('documentation_show', { path: document.metadata.path }),
document.metadata.title,
fa(document.metadata.icon ?? 'document', document.metadata.iconType ?? 'regular'),
path == document.metadata.path
) }}
{% endfor %}
</ul>
{% endfor %}

6
templates/document.html.twig Executable file
View File

@ -0,0 +1,6 @@
{% extends 'keylighter.html.twig' %}
{% block content %}
{{ document.content|markdown }}
{% endblock %}

7
templates/homepage.html.twig Executable file
View File

@ -0,0 +1,7 @@
{% extends "keylighter.html.twig" %}
{% set showForm = true %}
{% set current = 'about' %}
{% block content %}
{{ document.content | markdown }}
{% endblock %}

81
templates/keylighter.html.twig Executable file
View File

@ -0,0 +1,81 @@
{% import "macros.twig" as helper %}
{% set current = current is defined ? current : 'demo' %}
{% set showForm = showForm|default(false) %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>KeyLighter</title>
{{ encore_entry_link_tags('app') }}
<link rel="icon" href="{{ asset('img/favicon.png') }}"/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{{ url('homepage') }}">
<img src="{{ asset('img/logo.png') }}" alt="KeyLighter" class="d-inline-block align-top"/>
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse right" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item {% if showForm %}visible active{% endif %}" data-toggle="form" data-id="try-form" id="form-toggle">
<a href="#try-form" class="nav-link slim"><i class="{{ fa('chevron-down', 'solid') }}"></i></a>
</li>
{{ helper.menu_entry('https://github.com/kadet1090/keylighter', 'GitHub', fa('github', 'brand'), current == 'github') }}
{{ helper.menu_entry(url('changelog'), 'Changelog', fa('history'), current == 'changelog') }}
{{ helper.menu_entry(url('documentation_show'), 'Docs', fa('book'), current == 'docs') }}
{{ helper.menu_entry(url('homepage'), 'About', fa('info'), current == 'about') }}
</ul>
</div>
</div>
</nav>
<div id="try-form" class="{{ showForm ? 'visible' : 'hidden' }}">
<div class="container">
{% include 'demo/_form.html.twig' %}
</div>
</div>
<div class="container" id="content">
{% block content '' %}
</div>
<footer id="footer">
<div class="container">
<div class="row">
<div class="col-auto d-flex align-items-center">
<span style="margin-right: .5rem">KeyLighter</span>
<div class="dropup">
<button class="btn btn-primary dropdown-toggle btn-sm" type="button" title="Change KeyLighter version" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ versioner.current }}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
{% for version, hash in versioner.installedInfo %}
<a class="dropdown-item" href="?keylighter={{ version }}" title="{{ hash }}">{{ version }}</a>
{% endfor %}
</div>
</div>
</div>
<div class="col-auto align-content-center ml-auto">
<span>brought to you by</span>
<a href="#">
<img src="{{ asset('img/kadet.png') }}" alt="kadet.net" />
</a>
<span>&copy; 2017</span>
</div>
</div>
</div>
</footer>
{% block modals '' %}
{{ encore_entry_script_tags('app') }}
{% block scripts '' %}
</body>
</html>

10
templates/macros.twig Executable file
View File

@ -0,0 +1,10 @@
{% macro menu_entry(url, title, icon, current) %}
<li class="nav-item {% if current %}active{% endif %}">
<a class="nav-link" href="{{ url }}"><i class="fa-fw {{ icon }}"></i> {{ title }}</a>
</li>
{% endmacro %}
{% macro document_link(document) %}
<i class="fa-fw {{ fa(document.metadata.icon ?? 'document', document.metadata.iconType ?? 'regular') }}"></i>
{{ document.metadata.title }}
{% endmacro %}

13
tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"lib": ["dom", "es2015", "es2016.array.include", "es2017.object"],
"target": "es5",
"module": "esnext",
"sourceMap": true,
"noImplicitThis": true,
"moduleResolution": "node",
"downlevelIteration": true
},
"files": ["assets/ts/app.ts"],
"include": ["assets/ts/**/*.ts"]
}

27
webpack.config.js Normal file
View File

@ -0,0 +1,27 @@
const Encore = require('@symfony/webpack-encore');
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
.setOutputPath('public/build/')
.setPublicPath('/build')
.addEntry('app', './assets/ts/app.ts')
.splitEntryChunks()
.enableSingleRuntimeChunk()
.cleanupOutputBeforeBuild()
.enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
.enableVersioning(Encore.isProduction())
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = 3;
})
.autoProvidejQuery()
.autoProvideVariables({ Popper: ['popper.js', 'default'] })
.enableSassLoader()
.enableTypeScriptLoader()
;
module.exports = Encore.getWebpackConfig();

7190
yarn.lock Normal file

File diff suppressed because it is too large Load Diff