parent
11ffc370c2
commit
7a842eb58d
11 changed files with 1 additions and 461 deletions
.gitignore
site/plugins/kirby-highlighter
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -68,3 +68,4 @@ Icon
|
|||
# ---------------
|
||||
|
||||
/kirby
|
||||
/site/plugins
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020-PRESENT Johann Schopplich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,135 +0,0 @@
|
|||
# Kirby Highlighter
|
||||
|
||||
Server-side code highlighting available as [custom block](https://getkirby.com/docs/reference/panel/fields/blocks) and for [KirbyText](https://getkirby.com/docs/guide/content/text-formatting#kirbytext).
|
||||
|
||||
Built upon [highlight.php](http://www.highlightjs.org) which itself is a port of [highlight.js](http://www.highlightjs.org).
|
||||
|
||||
## Key Features
|
||||
|
||||
- 🏗 Works with Kirby's [`code` block](https://getkirby.com/docs/reference/panel/blocks/code)
|
||||
- 🏳️🌈 Supports 189 languages
|
||||
- 💫 94 styles available
|
||||
- ⛳️ Automatic language detection for KirbyText
|
||||
|
||||
## Requirements
|
||||
|
||||
- Kirby 3.8+
|
||||
|
||||
## Installation
|
||||
|
||||
### Composer
|
||||
|
||||
```
|
||||
composer require johannschopplich/kirby-highlighter
|
||||
```
|
||||
|
||||
### Download
|
||||
|
||||
Download and copy this repository to `/site/plugins/kirby-highlighter`.
|
||||
|
||||
## Usage
|
||||
|
||||
### With Kirby Blocks Field
|
||||
|
||||
This plugin overwrites Kirby's internal [`code` block](https://getkirby.com/docs/reference/panel/blocks/code). Thus, you won't have to change a thing.
|
||||
|
||||
Use the `code` block just like before, the output will be highlighted automatically:
|
||||
|
||||
```yaml
|
||||
fields:
|
||||
example:
|
||||
label: Paste code here
|
||||
type: blocks
|
||||
fieldsets:
|
||||
- code
|
||||
```
|
||||
|
||||
### Within KirbyText
|
||||
|
||||
Create a code block in your KirbyText field and optionally set the code language:
|
||||
|
||||
<pre lang="no-highlight"><code>```css
|
||||
.currentColor {
|
||||
color: currentColor;
|
||||
}
|
||||
```
|
||||
</code></pre>
|
||||
|
||||
Or use the new `code`-KirbyTag from this plugin with a base64 encoded code string:
|
||||
|
||||
```
|
||||
(code: LmN1cnJlbnRDb2xvciB7CiAgY29sb3I6IGN1cnJlbnRDb2xvcjsKfQ== lang: css)
|
||||
```
|
||||
|
||||
Which outputs:
|
||||
|
||||
```html
|
||||
<pre class="hljs"><code><span class="hljs-selector-class">.currentColor</span> {
|
||||
<span class="hljs-attribute">color</span>: currentColor;
|
||||
}</code></pre>
|
||||
```
|
||||
|
||||
The syntax highlighting functionality can be changed. You can choose between two highlighting modes:
|
||||
|
||||
1. Explicit mode (default)
|
||||
2. Automatic language detection mode (opt-in)
|
||||
|
||||
#### Explicit Mode
|
||||
|
||||
In explicit mode, you have to define which language the code block is. Otherwise highlighting will be skipped.
|
||||
|
||||
#### Automatic Language Detection
|
||||
|
||||
Alternatively you can use the automatic detection mode, which highlights your code with the language the library thinks is best. It is highly recommended you explicitly choose the language or limit the number of languages to automatically detect from. This reduces the number of inaccuracies and skips this extremely inefficient selection process.
|
||||
|
||||
To enable automatic language detection, set:
|
||||
|
||||
- `johannschopplich.highlighter.autodetect` to `true`
|
||||
- `johannschopplich.highlighter.languages` to an array of names from which languages should be chosen
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| --------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `johannschopplich.highlighter.class` | `hljs` | Style class for Highlight to be added to the `pre` element. |
|
||||
| `johannschopplich.highlighter.autodetect` | `false` | Indicates if the library should define which language thinks is best. Only applies when no language was set on the KirbyText code block. |
|
||||
| `johannschopplich.highlighter.languages` | `[]` | Array of language names to be auto-detected. If empty, every language will be auto-detectable. |
|
||||
| `johannschopplich.highlighter.line-numbering` | `false` | Indicates if the library should split up the highlighted code on each new line and wrap it in a `<span>` element. |
|
||||
| `johannschopplich.highlighter.line-numbering-class` | `hljs-code-line` | CSS class applied to highlighted code lines, respectively `<span>` elements. |
|
||||
|
||||
## Styling in the Frontend
|
||||
|
||||
Since this plugin handles highlighting code only and thus just wraps span's around code, you have to link styles in your frontend yourself. I recommend choosing one of the available themes directly from the highlight.js project: [highlight.js/src/styles/](https://github.com/highlightjs/highlight.js/tree/master/src/styles)
|
||||
|
||||
The CSS files over at the repository are maintained and new ones arrive from time to time, therefore it would be redundant to include a copy in this repository.
|
||||
|
||||
One of my favorite themes is [Night Owl by Sarah Drasner](https://github.com/highlightjs/highlight.js/blob/master/src/styles/night-owl.css).
|
||||
For example you could download the CSS file and save it in your Kirby project under `assets/css/hljs-night-owl.css`. Now you just have to include it in your template `<?= css('assets/css/hljs-night-owl.css') ?>`. Alternatively, use a CSS bundler of your choice.
|
||||
|
||||
### Line Numbering
|
||||
|
||||
If you choose to activate the line numbering option, you will need to include additional CSS style to display line numbering.
|
||||
|
||||
A basic example using [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements):
|
||||
|
||||
```css
|
||||
pre.hljs .hljs-code-line {
|
||||
counter-increment: line;
|
||||
}
|
||||
|
||||
pre.hljs .hljs-code-line::before {
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
opacity: 0.5;
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
- Geert Bergman and contributors for the awesome [highlight.php](https://github.com/scrivo/highlight.php) port.
|
||||
- Martin Folkers for his [Kirby Highlight](https://github.com/S1SYPHOS/kirby3-highlight) plugin which built the base of this package.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE) License © 2020-PRESENT [Johann Schopplich](https://github.com/johannschopplich)
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace JohannSchopplich;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMNode;
|
||||
|
||||
class HTML5DOMDocument extends DOMDocument
|
||||
{
|
||||
/**
|
||||
* Name of temporary root element for the XML parser
|
||||
*/
|
||||
protected string $tempRoot = 'main';
|
||||
|
||||
/**
|
||||
* Create a new HTML5-compatible document parser
|
||||
*/
|
||||
public function __construct(string $version = '1.0', string $encoding = 'UTF-8')
|
||||
{
|
||||
// Silence libxml errors with HTML5 elements
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
// Call parent class
|
||||
parent::__construct($version, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load HTML from string, make UTF-8 compatible and add temporary root element
|
||||
*/
|
||||
public function loadHTML(string $source, int $options = LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD): bool
|
||||
{
|
||||
// `loadHTML` will treat the string as being in ISO-8859-1 unless
|
||||
// told otherwise, so translate anything above the ASCII range into
|
||||
// its html entity equivalent
|
||||
// @see https://stackoverflow.com/questions/39148170/utf-8-with-php-domdocument-loadhtml
|
||||
$convertedSource = htmlspecialchars_decode(htmlentities($source, ENT_COMPAT, 'UTF-8'), ENT_QUOTES);
|
||||
|
||||
// Add fake root element for XML parser because it assumes that the
|
||||
// first encountered tag is the root element
|
||||
// @see https://stackoverflow.com/questions/39479994/php-domdocument-savehtml-breaks-format
|
||||
return parent::loadHTML("<{$this->tempRoot}>" . $convertedSource . "</{$this->tempRoot}>", $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the temporarily added root element
|
||||
*/
|
||||
private function unwrapTempRoot(string $output): string
|
||||
{
|
||||
if ($this->firstChild->nodeName === $this->tempRoot) {
|
||||
return substr($output, strlen($this->tempRoot) + 2, -strlen($this->tempRoot) - 4);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the internal document into a HTML string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function saveHTML(DOMNode|null $node = null, bool $entities = false): string|false
|
||||
{
|
||||
$html = parent::saveHTML($node);
|
||||
|
||||
if ($entities === false) {
|
||||
$html = html_entity_decode($html);
|
||||
}
|
||||
|
||||
if ($node === null) {
|
||||
$html = $this->unwrapTempRoot($html);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"name": "johannschopplich/kirby-highlighter",
|
||||
"description": "Server-side syntax highlighting for Kirby CMS",
|
||||
"type": "kirby-plugin",
|
||||
"version": "3.1.0",
|
||||
"keywords": [
|
||||
"kirby",
|
||||
"highlight",
|
||||
"highlighter",
|
||||
"hljs"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/johannschopplich/kirby-highlighter#readme",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johann Schopplich",
|
||||
"email": "pkg@johannschopplich.com",
|
||||
"homepage": "https://johannschopplich.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"getkirby/composer-installer": "^1.2",
|
||||
"scrivo/highlight.php": "^9.18"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "@stable",
|
||||
"getkirby/cms": "^4",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JohannSchopplich\\": "classes/JohannSchopplich/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"fix": "php-cs-fixer fix",
|
||||
"dist": "composer install --no-dev --optimize-autoloader",
|
||||
"test": "phpunit"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"allow-plugins": {
|
||||
"getkirby/composer-installer": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"kirby-cms-path": "tests/fixtures/kirby"
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('is_base64_string_s')) {
|
||||
// https://stackoverflow.com/a/51877882
|
||||
function is_base64_string_s(string $str, $enc = ['UTF-8', 'ASCII'])
|
||||
{
|
||||
return !(($b = base64_decode($str, true)) === false) && in_array(mb_detect_encoding($b), $enc);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'fromBase64' => function ($field) {
|
||||
if ($field->isNotEmpty()) {
|
||||
$value = trim((string)$field->value());
|
||||
|
||||
if (is_base64_string_s($value)) {
|
||||
$field->value = base64_decode($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $field;
|
||||
},
|
||||
];
|
|
@ -1,86 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Highlight\Highlighter;
|
||||
use JohannSchopplich\HTML5DOMDocument;
|
||||
use Kirby\Cms\App;
|
||||
|
||||
return [
|
||||
'kirbytext:after' => function (string|null $text) {
|
||||
$kirby = App::instance();
|
||||
|
||||
// Parse KirbyText input as HTML document
|
||||
$dom = new HTML5DOMDocument();
|
||||
$dom->loadHTML($text);
|
||||
|
||||
// Retrieve all `pre` elements inside newly created HTML document
|
||||
$preNodes = $dom->getElementsByTagName('pre');
|
||||
|
||||
// Bail if no `pre` elements have been found
|
||||
if ($preNodes->length === 0) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
// Loop through all `pre` elements
|
||||
foreach ($preNodes as $preNode) {
|
||||
// Ensure nothing but the `code` element exists
|
||||
if ($preNode->childNodes->length !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Select direct `code` child element of `pre` block
|
||||
$codeNode = $preNode->firstChild;
|
||||
|
||||
// Get language code if present
|
||||
$language = $codeNode->getAttribute('class');
|
||||
if (str_starts_with($language, 'language-')) {
|
||||
$language = preg_replace('/^language-/', '', $language);
|
||||
}
|
||||
|
||||
// Bail highlighting if language isn't set and auto detection is disabled
|
||||
if (empty($language) && !$kirby->option('johannschopplich.highlighter.autodetect', false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add `hljs` class to `pre` block
|
||||
$preNode->setAttribute('class', $kirby->option('johannschopplich.highlighter.class', 'hljs'));
|
||||
|
||||
// Get raw code data to highlight
|
||||
$code = $codeNode->nodeValue;
|
||||
|
||||
// Remove code element afterwards
|
||||
$preNode->removeChild($codeNode);
|
||||
|
||||
// Initiate `Highlighter` and use pre-defined language code, fall
|
||||
// back to language auto detection if enabled
|
||||
$highlighter = new Highlighter();
|
||||
|
||||
// Highlight code
|
||||
if (!empty($language)) {
|
||||
$highlightedCode = $highlighter->highlight($language, $code);
|
||||
} elseif ($kirby->option('johannschopplich.highlighter.autodetect', false)) {
|
||||
$languageSubset = $kirby->option('johannschopplich.highlighter.languages', []);
|
||||
if (!empty($languageSubset)) {
|
||||
$highlighter->setAutodetectLanguages($languageSubset);
|
||||
}
|
||||
|
||||
$highlightedCode = $highlighter->highlightAuto($code);
|
||||
}
|
||||
|
||||
// Line numbering
|
||||
if ($kirby->option('johannschopplich.highlighter.line-numbering', false)) {
|
||||
$lines = preg_split('/\R/', $highlightedCode->value);
|
||||
$lineClass = $kirby->option('johannschopplich.highlighter.line-numbering-class', 'hljs-code-line');
|
||||
$highlightedCode->value = '<span class="' . $lineClass . '">' . implode("</span>\n<span class=\"$lineClass\">", $lines) . '</span>';
|
||||
}
|
||||
|
||||
// Append highlighted wrapped in `code` block to parent `pre`
|
||||
$codeNode = $dom->createDocumentFragment();
|
||||
$codeNode->appendXML('<code data-language="' . $language . '">' . $highlightedCode->value . '</code>');
|
||||
$preNode->appendChild($codeNode);
|
||||
}
|
||||
|
||||
// Save all changes
|
||||
$text = $dom->saveHTML(null, true);
|
||||
return $text;
|
||||
}
|
||||
];
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Kirby\Cms\App;
|
||||
|
||||
return [
|
||||
'code' => [
|
||||
'attr' => [
|
||||
'lang',
|
||||
'language',
|
||||
],
|
||||
// TODO: Type as `\Kirby\Text\KirbyTag` for Kirby 4
|
||||
'html' => function ($tag) {
|
||||
$kirby = App::instance();
|
||||
$code = $tag->value;
|
||||
$language = $tag->lang ?? $tag->language;
|
||||
$block = new \Kirby\Cms\Block([
|
||||
'type' => 'code',
|
||||
'content' => [
|
||||
'language' => $language ?? 'plaintext',
|
||||
'code' => is_base64_string_s($code) ? base64_decode($code) : $code,
|
||||
]
|
||||
]);
|
||||
|
||||
return $kirby->snippet('blocks/code', ['block' => $block], true);
|
||||
}
|
||||
]
|
||||
];
|
|
@ -1,12 +0,0 @@
|
|||
<?php
|
||||
|
||||
@include_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
\Kirby\Cms\App::plugin('johannschopplich/highlighter', [
|
||||
'hooks' => require __DIR__ . '/extensions/hooks.php',
|
||||
'fieldMethods' => require __DIR__ . '/extensions/fieldmethods.php',
|
||||
'tags' => require __DIR__ . '/extensions/kirbytags.php',
|
||||
'snippets' => [
|
||||
'blocks/code' => __DIR__ . '/snippets/blocks/code.php'
|
||||
]
|
||||
]);
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
verbose="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="classes">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
/** @var \Kirby\Cms\Block $block */
|
||||
$highlighter = new \Highlight\Highlighter();
|
||||
$language = $block->language()->value();
|
||||
$code = $block->code()->fromBase64()->value();
|
||||
|
||||
if (empty($language) || !in_array($language, $highlighter->listRegisteredLanguages())) {
|
||||
$language = 'plaintext';
|
||||
}
|
||||
|
||||
$highlightedCode = $highlighter->highlight($language, $code)->value;
|
||||
|
||||
// Handle line numbering
|
||||
if (option('johannschopplich.highlighter.line-numbering', false)) {
|
||||
$lines = preg_split('/\R/', $highlightedCode);
|
||||
$lineClass = option('johannschopplich.highlighter.line-numbering-class', 'hljs-code-line');
|
||||
$highlightedCode = '<span class="' . $lineClass . '">' . implode("</span>\n<span class=\"$lineClass\">", $lines) . '</span>';
|
||||
}
|
||||
|
||||
?>
|
||||
<pre class="<?= option('johannschopplich.highlighter.class', 'hljs') ?>"><code data-language="<?= $language ?>"><?= $highlightedCode ?></code></pre>
|
Loading…
Reference in a new issue