├── .gitignore
├── LICENSE
├── README.md
├── conftest.py
├── docs
└── img
│ └── screenshot-editor.png
├── manage.py
├── pyproject.toml
├── tests
├── __init__.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── settings.py
├── test_blocks.py
└── urls.py
└── wagtailcodeblock
├── __init__.py
├── blocks.py
├── settings.py
├── static
└── wagtailcodeblock
│ ├── css
│ ├── wagtail-code-block.css
│ └── wagtail-code-block.min.css
│ └── js
│ └── wagtailcodeblock.js
├── templates
└── wagtailcodeblock
│ ├── code_block.html
│ ├── code_block_form.html
│ └── raw_code.html
├── templatetags
├── __init__.py
└── wagtailcodeblock_tags.py
└── wagtail_hooks.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore build, tests, and venvs
2 | build/*
3 | dist/*
4 | htmlcov/*
5 | venv/*
6 | wagtailcodeblock.egg-info/*
7 | test_db.sqlite3
8 | wagtailcodeblock/_version.py
9 |
10 | # manage.py, always default to dev settings
11 | manage.py
12 |
13 | # Django media files
14 | media/*
15 |
16 | # Python bytecode:
17 | *.py[co]
18 |
19 | # Packaging files:
20 | *.egg*
21 |
22 | # Sphinx docs:
23 | build
24 |
25 | # SQLite3 database files:
26 | *.db
27 |
28 | # Logs:
29 | *.log
30 |
31 | # Eclipse
32 | .project
33 |
34 | # Linux Editors
35 | *~
36 | \#*\#
37 | /.emacs.desktop
38 | /.emacs.desktop.lock
39 | .elc
40 | auto-save-list
41 | tramp
42 | .\#*
43 | *.swp
44 | *.swo
45 |
46 | # Mac
47 | .DS_Store
48 | ._*
49 |
50 | # Windows
51 | Thumbs.db
52 | Desktop.ini
53 |
54 | # Dev tools
55 | .idea
56 | .vagrant
57 |
58 | # Ignore local configurations
59 | wrds/settings/local.py
60 | db.sqlite3
61 |
62 | # Node modules
63 | node_modules/
64 |
65 | # Bower components
66 | bower_components/
67 |
68 | # Coverage
69 | htmlcov/
70 | .coverage
71 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Timothy Allen and individual contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of Timothy Allen, The Wharton School, nor the names of
15 | its contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Wagtail Code Block
2 |
3 | Wagtail Code Block is a syntax highlighter block for source code for the Wagtail CMS. It features real-time highlighting in the Wagtail editor, the front end, line numbering, and support for PrismJS themes.
4 |
5 | It uses the [PrismJS](http://prismjs.com/) library both in Wagtail Admin and the website.
6 |
7 | ## Example Usage
8 |
9 | First, add `wagtailcodeblock` to your `INSTALLED_APPS` in Django's settings. Here's a bare bones example:
10 |
11 | ```python
12 | from wagtail.blocks import TextBlock
13 | from wagtail.fields import StreamField
14 | from wagtail.models import Page
15 | from wagtail.admin.panels import FieldPanel
16 |
17 | from wagtailcodeblock.blocks import CodeBlock
18 |
19 |
20 | class HomePage(Page):
21 | body = StreamField([
22 | ("heading", TextBlock()),
23 | ("code", CodeBlock(label='Code')),
24 | ])
25 |
26 | content_panels = Page.content_panels + [
27 | FieldPanel("body"),
28 | ]
29 | ```
30 |
31 | You can also force it to use a single language or set a default language by providing a language code which must be included in your `WAGTAIL_CODE_BLOCK_LANGUAGES` setting:
32 |
33 | ```python
34 | bash_code = CodeBlock(label='Bash Code', language='bash')
35 | any_code = CodeBlock(label='Any code', default_language='python')
36 | ```
37 |
38 | ## Screenshot of the CMS Editor Interface
39 |
40 | 
41 |
42 | ## Installation & Setup
43 |
44 | To install Wagtail Code Block run:
45 |
46 | ```bash
47 | # Wagtail 4.0 and greater
48 | pip install wagtailcodeblock
49 |
50 | # Wagtail 3.x
51 | pip install wagtailcodeblock==1.28.0.0
52 |
53 | # Wagtail 2.x
54 | pip install wagtailcodeblock==1.25.0.2
55 | ```
56 |
57 | And add `wagtailcodeblock` to your `INSTALLED_APPS` setting:
58 |
59 | ```python
60 | INSTALLED_APPS = [
61 | ...
62 | 'wagtailcodeblock',
63 | ...
64 | ]
65 | ```
66 |
67 | ## Django Settings
68 |
69 | ### Line Numbers
70 |
71 | Line numbers are enabled by default, but can be disabled in Django's settings:
72 |
73 | ```python
74 | WAGTAIL_CODE_BLOCK_LINE_NUMBERS = False
75 | ```
76 |
77 | ### Copy to clipboard
78 |
79 | Copy to clipboard is enabled by default, but can be disabled in Django's settings:
80 |
81 | ```python
82 | WAGTAIL_CODE_BLOCK_COPY_TO_CLIPBOARD = False
83 | ```
84 |
85 | ### Themes
86 |
87 | Wagtail Code Block defaults to the PrismJS "Coy" theme, which looks good with Wagtail's CMS editor design. You can choose a different theme by configuring `WAGTAIL_CODE_BLOCK_THEME` in your Django settings. PrismJS provides several themes:
88 |
89 | * **None**: Default
90 | * **'coy'**: Coy
91 | * **'dark'**: Dark
92 | * **'funky'**: Funky
93 | * **'okaidia'**: Okaidia
94 | * **'solarizedlight'**: Solarized Light
95 | * **'twilight'**: Twilight
96 |
97 | For example, in you want to use the Solarized Light theme: `WAGTAIL_CODE_BLOCK_THEME = 'solarizedlight'`
98 | If you want to use the Default theme: `WAGTAIL_CODE_BLOCK_THEME = None`
99 |
100 | ### Languages Available
101 |
102 | You can customize the languages available by configuring `WAGTAIL_CODE_BLOCK_LANGUAGES` in your Django settings. By default, it will be set with these languages, since most users are in the Python web development community:
103 |
104 | ```python
105 | WAGTAIL_CODE_BLOCK_LANGUAGES = (
106 | ('bash', 'Bash/Shell'),
107 | ('css', 'CSS'),
108 | ('diff', 'diff'),
109 | ('html', 'HTML'),
110 | ('javascript', 'Javascript'),
111 | ('json', 'JSON'),
112 | ('python', 'Python'),
113 | ('scss', 'SCSS'),
114 | ('yaml', 'YAML'),
115 | )
116 | ```
117 |
118 | Each language in this setting is a tuple of the PrismJS code and a descriptive label. If you want use all available languages, here is a list:
119 |
120 | ```python
121 | WAGTAIL_CODE_BLOCK_LANGUAGES = (
122 | ('abap', 'ABAP'),
123 | ('abnf', 'Augmented Backus–Naur form'),
124 | ('actionscript', 'ActionScript'),
125 | ('ada', 'Ada'),
126 | ('antlr4', 'ANTLR4'),
127 | ('apacheconf', 'Apache Configuration'),
128 | ('apl', 'APL'),
129 | ('applescript', 'AppleScript'),
130 | ('aql', 'AQL'),
131 | ('arduino', 'Arduino'),
132 | ('arff', 'ARFF'),
133 | ('asciidoc', 'AsciiDoc'),
134 | ('asm6502', '6502 Assembly'),
135 | ('aspnet', 'ASP.NET (C#)'),
136 | ('autohotkey', 'AutoHotkey'),
137 | ('autoit', 'AutoIt'),
138 | ('bash', 'Bash + Shell'),
139 | ('basic', 'BASIC'),
140 | ('batch', 'Batch'),
141 | ('bison', 'Bison'),
142 | ('bnf', 'Backus–Naur form + Routing Backus–Naur form'),
143 | ('brainfuck', 'Brainfuck'),
144 | ('bro', 'Bro'),
145 | ('c', 'C'),
146 | ('clike', 'C-like'),
147 | ('cmake', 'CMake'),
148 | ('csharp', 'C#'),
149 | ('cpp', 'C++'),
150 | ('cil', 'CIL'),
151 | ('coffeescript', 'CoffeeScript'),
152 | ('clojure', 'Clojure'),
153 | ('crystal', 'Crystal'),
154 | ('csp', 'Content-Security-Policy'),
155 | ('css', 'CSS'),
156 | ('css-extras', 'CSS Extras'),
157 | ('d', 'D'),
158 | ('dart', 'Dart'),
159 | ('diff', 'Diff'),
160 | ('django', 'Django/Jinja2'),
161 | ('dns-zone-file', 'DNS Zone File'),
162 | ('docker', 'Docker'),
163 | ('ebnf', 'Extended Backus–Naur form'),
164 | ('eiffel', 'Eiffel'),
165 | ('ejs', 'EJS'),
166 | ('elixir', 'Elixir'),
167 | ('elm', 'Elm'),
168 | ('erb', 'ERB'),
169 | ('erlang', 'Erlang'),
170 | ('etlua', 'Embedded LUA Templating'),
171 | ('fsharp', 'F#'),
172 | ('flow', 'Flow'),
173 | ('fortran', 'Fortran'),
174 | ('ftl', 'Freemarker Template Language'),
175 | ('gcode', 'G-code'),
176 | ('gdscript', 'GDScript'),
177 | ('gedcom', 'GEDCOM'),
178 | ('gherkin', 'Gherkin'),
179 | ('git', 'Git'),
180 | ('glsl', 'GLSL'),
181 | ('gml', 'GameMaker Language'),
182 | ('go', 'Go'),
183 | ('graphql', 'GraphQL'),
184 | ('groovy', 'Groovy'),
185 | ('haml', 'Haml'),
186 | ('handlebars', 'Handlebars'),
187 | ('haskell', 'Haskell'),
188 | ('haxe', 'Haxe'),
189 | ('hcl', 'HCL'),
190 | ('http', 'HTTP'),
191 | ('hpkp', 'HTTP Public-Key-Pins'),
192 | ('hsts', 'HTTP Strict-Transport-Security'),
193 | ('ichigojam', 'IchigoJam'),
194 | ('icon', 'Icon'),
195 | ('inform7', 'Inform 7'),
196 | ('ini', 'Ini'),
197 | ('io', 'Io'),
198 | ('j', 'J'),
199 | ('java', 'Java'),
200 | ('javadoc', 'JavaDoc'),
201 | ('javadoclike', 'JavaDoc-like'),
202 | ('javascript', 'JavaScript'),
203 | ('javastacktrace', 'Java stack trace'),
204 | ('jolie', 'Jolie'),
205 | ('jq', 'JQ'),
206 | ('jsdoc', 'JSDoc'),
207 | ('js-extras', 'JS Extras'),
208 | ('js-templates', 'JS Templates'),
209 | ('json', 'JSON'),
210 | ('jsonp', 'JSONP'),
211 | ('json5', 'JSON5'),
212 | ('julia', 'Julia'),
213 | ('keyman', 'Keyman'),
214 | ('kotlin', 'Kotlin'),
215 | ('latex', 'LaTeX'),
216 | ('less', 'Less'),
217 | ('lilypond', 'Lilypond'),
218 | ('liquid', 'Liquid'),
219 | ('lisp', 'Lisp'),
220 | ('livescript', 'LiveScript'),
221 | ('lolcode', 'LOLCODE'),
222 | ('lua', 'Lua'),
223 | ('makefile', 'Makefile'),
224 | ('markdown', 'Markdown'),
225 | ('markup', 'Markup + HTML + XML + SVG + MathML'),
226 | ('markup-templating', 'Markup templating'),
227 | ('matlab', 'MATLAB'),
228 | ('mel', 'MEL'),
229 | ('mizar', 'Mizar'),
230 | ('monkey', 'Monkey'),
231 | ('n1ql', 'N1QL'),
232 | ('n4js', 'N4JS'),
233 | ('nand2tetris-hdl', 'Nand To Tetris HDL'),
234 | ('nasm', 'NASM'),
235 | ('nginx', 'nginx'),
236 | ('nim', 'Nim'),
237 | ('nix', 'Nix'),
238 | ('nsis', 'NSIS'),
239 | ('objectivec', 'Objective-C'),
240 | ('ocaml', 'OCaml'),
241 | ('opencl', 'OpenCL'),
242 | ('oz', 'Oz'),
243 | ('parigp', 'PARI/GP'),
244 | ('parser', 'Parser'),
245 | ('pascal', 'Pascal + Object Pascal'),
246 | ('pascaligo', 'Pascaligo'),
247 | ('pcaxis', 'PC Axis'),
248 | ('perl', 'Perl'),
249 | ('php', 'PHP'),
250 | ('phpdoc', 'PHPDoc'),
251 | ('php-extras', 'PHP Extras'),
252 | ('plsql', 'PL/SQL'),
253 | ('powershell', 'PowerShell'),
254 | ('processing', 'Processing'),
255 | ('prolog', 'Prolog'),
256 | ('properties', '.properties'),
257 | ('protobuf', 'Protocol Buffers'),
258 | ('pug', 'Pug'),
259 | ('puppet', 'Puppet'),
260 | ('pure', 'Pure'),
261 | ('python', 'Python'),
262 | ('q', 'Q (kdb+ database)'),
263 | ('qore', 'Qore'),
264 | ('r', 'R'),
265 | ('jsx', 'React JSX'),
266 | ('tsx', 'React TSX'),
267 | ('renpy', 'Ren\'py'),
268 | ('reason', 'Reason'),
269 | ('regex', 'Regex'),
270 | ('rest', 'reST (reStructuredText)'),
271 | ('rip', 'Rip'),
272 | ('roboconf', 'Roboconf'),
273 | ('robot-framework', 'Robot Framework'),
274 | ('ruby', 'Ruby'),
275 | ('rust', 'Rust'),
276 | ('sas', 'SAS'),
277 | ('sass', 'Sass (Sass)'),
278 | ('scss', 'Sass (Scss)'),
279 | ('scala', 'Scala'),
280 | ('scheme', 'Scheme'),
281 | ('shell-session', 'Shell Session'),
282 | ('smalltalk', 'Smalltalk'),
283 | ('smarty', 'Smarty'),
284 | ('solidity', 'Solidity (Ethereum)'),
285 | ('sparql', 'SPARQL'),
286 | ('splunk-spl', 'Splunk SPL'),
287 | ('sqf', 'SQF: Status Quo Function (Arma 3)'),
288 | ('sql', 'SQL'),
289 | ('soy', 'Soy (Closure Template)'),
290 | ('stylus', 'Stylus'),
291 | ('swift', 'Swift'),
292 | ('tap', 'TAP'),
293 | ('tcl', 'Tcl'),
294 | ('textile', 'Textile'),
295 | ('toml', 'TOML'),
296 | ('tt2', 'Template Toolkit 2'),
297 | ('twig', 'Twig'),
298 | ('typescript', 'TypeScript'),
299 | ('t4-cs', 'T4 Text Templates (C#)'),
300 | ('t4-vb', 'T4 Text Templates (VB)'),
301 | ('t4-templating', 'T4 templating'),
302 | ('vala', 'Vala'),
303 | ('vbnet', 'VB.Net'),
304 | ('velocity', 'Velocity'),
305 | ('verilog', 'Verilog'),
306 | ('vhdl', 'VHDL'),
307 | ('vim', 'vim'),
308 | ('visual-basic', 'Visual Basic'),
309 | ('wasm', 'WebAssembly'),
310 | ('wiki', 'Wiki markup'),
311 | ('xeora', 'Xeora + XeoraCube'),
312 | ('xojo', 'Xojo (REALbasic)'),
313 | ('xquery', 'XQuery'),
314 | ('yaml', 'YAML'),
315 | ('zig', 'Zig'),
316 | )
317 | ```
318 |
319 | # What's With the Versioning?
320 |
321 | Our version numbers are based on the underlying version of PrismJS we use. For example, if we are using PrismJS `1.28.0`, our versions will be named `1.28.0.X`.
322 |
323 | # Running the Test Suite
324 |
325 | Clone the repository, create a `venv`, `pip install -e .[dev]` and run `pytest`.
326 |
327 | # Release Notes & Contributors
328 |
329 | * Thank you to our [wonderful contributors](https://github.com/FlipperPA/wagtailcodeblock/graphs/contributors)!
330 | * Release notes are [available on GitHub](https://github.com/FlipperPA/wagtailcodeblock/releases).
331 |
332 | # Project Maintainers
333 |
334 | * Timothy Allen (https://github.com/FlipperPA)
335 | * Milton Lenis (https://github.com/MiltonLn)
336 |
337 | This package was created by the staff of [Wharton Research Data Services](https://wrds.wharton.upenn.edu/). We are thrilled that [The Wharton School](https://www.wharton.upenn.edu/) allows us a certain amount of time to contribute to open-source projects. We add features as they are necessary for our projects, and try to keep up with Issues and Pull Requests as best we can. Due to constraints of time (our full time jobs!), Feature Requests without a Pull Request may not be implemented, but we are always open to new ideas and grateful for contributions and our users.
338 |
--------------------------------------------------------------------------------
/conftest.py:
--------------------------------------------------------------------------------
1 | from json import dumps
2 |
3 | from django.contrib.contenttypes.models import ContentType
4 |
5 | import pytest
6 | from wagtail.models import Page, Locale, Site
7 |
8 | from tests.models import CodeBlockPage
9 |
10 |
11 | @pytest.fixture
12 | def test_page(db):
13 | """
14 | Create a root page in the same way Wagtail does in migrations. See:
15 | https://github.com/wagtail/wagtail/blob/main/wagtail/core/migrations/0002_initial_data.py#L12 # noqa
16 |
17 | Then create the test page.
18 | """
19 | page_content_type, created = ContentType.objects.get_or_create(
20 | model="page", app_label="wagtailcore"
21 | )
22 |
23 | root_page, created = Page.objects.get_or_create(
24 | title="Root",
25 | slug="root",
26 | content_type=page_content_type,
27 | path="0001",
28 | depth=1,
29 | numchild=1,
30 | url_path="/",
31 | )
32 |
33 | test_page, created = CodeBlockPage.objects.get_or_create(
34 | # Required Wagtail Page fields
35 | title="TEST Wagtail Code Block Page",
36 | slug="wagtail-code-block",
37 | content_type=page_content_type,
38 | path="00010002",
39 | depth=2,
40 | numchild=0,
41 | url_path="/wagtail-code-block/",
42 | # Wagtail Code Block test fields
43 | body=dumps([{
44 | "type": "code",
45 | "value": {
46 | "language": "python",
47 | "code": "print([x for x in range(1, 5)])",
48 | },
49 | }]),
50 | )
51 |
52 | return test_page
53 |
--------------------------------------------------------------------------------
/docs/img/screenshot-editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlipperPA/wagtailcodeblock/11aaf676b236c22bceac8c9cf778a1f55003ccb9/docs/img/screenshot-editor.png
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings')
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError as exc:
12 | raise ImportError(
13 | "Couldn't import Django. Are you sure it's installed and "
14 | "available on your PYTHONPATH environment variable? Did you "
15 | "forget to activate a virtual environment?"
16 | ) from exc
17 | execute_from_command_line(sys.argv)
18 |
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "wagtailcodeblock"
3 | authors = [{name = "Tim Allen", email = "tallen@wharton.upenn.edu"},]
4 | description = "Wagtail Code Block provides PrismJS syntax highlighting in Wagtail."
5 | dynamic = ["version"]
6 | readme = "README.md"
7 | requires-python = ">=3.7"
8 | keywords = ["wagtail", "cms", "contact", "syntax", "code", "highlighting", "highlighter"]
9 | license = {text = "BSD-3-Clause"}
10 | classifiers = [
11 | "Development Status :: 5 - Production/Stable",
12 | "Environment :: Web Environment",
13 | "Intended Audience :: Developers",
14 | "License :: OSI Approved :: BSD License",
15 | "Operating System :: OS Independent",
16 | "Programming Language :: Python :: 3",
17 | "Programming Language :: Python :: 3.8",
18 | "Programming Language :: Python :: 3.9",
19 | "Programming Language :: Python :: 3.10",
20 | "Programming Language :: Python :: 3.11",
21 | "Programming Language :: Python :: 3.12",
22 | "Framework :: Django",
23 | "Framework :: Wagtail",
24 | "Framework :: Wagtail :: 3",
25 | "Framework :: Wagtail :: 4",
26 | "Framework :: Wagtail :: 5",
27 | "Framework :: Wagtail :: 6",
28 | "Topic :: Internet :: WWW/HTTP",
29 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
30 | ]
31 | dependencies = [
32 | "wagtail>=4",
33 | ]
34 |
35 | [project.optional-dependencies]
36 | dev = [
37 | "django-coverage-plugin",
38 | "ipython",
39 | "ruff",
40 | "pytest-coverage",
41 | "pytest-django",
42 | ]
43 |
44 | [project.urls]
45 | "Homepage" = "https://github.com/FlipperPA/wagtailcodeblock"
46 | "Repository" = "https://github.com/FlipperPA/wagtailcodeblock"
47 | "Documentation" = "https://github.com/FlipperPA/wagtailcodeblock"
48 |
49 | [build-system]
50 | requires = ["setuptools>=67", "setuptools_scm>=7", "wheel"]
51 | build-backend = "setuptools.build_meta"
52 |
53 | [tool.setuptools_scm]
54 | write_to = "wagtailcodeblock/_version.py"
55 |
56 | [tool.pytest.ini_options]
57 | addopts = "--cov --cov-report=html"
58 | python_files = "tests.py test_*.py"
59 | DJANGO_SETTINGS_MODULE = "tests.settings"
60 |
61 | [tool.coverage.run]
62 | plugins = ["django_coverage_plugin"]
63 | include = ["wagtailcodeblock/*"]
64 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlipperPA/wagtailcodeblock/11aaf676b236c22bceac8c9cf778a1f55003ccb9/tests/__init__.py
--------------------------------------------------------------------------------
/tests/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.6 on 2019-10-23 15:29
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | import wagtail.blocks
6 | import wagtail.fields
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | ("wagtailcore", "0041_group_collection_permissions_verbose_name_plural"),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name="CodeBlockPage",
20 | fields=[
21 | (
22 | "page_ptr",
23 | models.OneToOneField(
24 | auto_created=True,
25 | on_delete=django.db.models.deletion.CASCADE,
26 | parent_link=True,
27 | primary_key=True,
28 | serialize=False,
29 | to="wagtailcore.Page",
30 | ),
31 | ),
32 | (
33 | "body",
34 | wagtail.fields.StreamField(
35 | [
36 | (
37 | "code",
38 | wagtail.blocks.StructBlock(
39 | [
40 | (
41 | "language",
42 | wagtail.blocks.ChoiceBlock(
43 | choices=[
44 | ("bash", "Bash/Shell"),
45 | ("css", "CSS"),
46 | ("diff", "diff"),
47 | ("html", "HTML"),
48 | ("javascript", "Javascript"),
49 | ("json", "JSON"),
50 | ("python", "Python"),
51 | ("scss", "SCSS"),
52 | ("yaml", "YAML"),
53 | ],
54 | help_text="Coding language",
55 | identifier="language",
56 | label="Language",
57 | ),
58 | ),
59 | (
60 | "code",
61 | wagtail.blocks.TextBlock(
62 | identifier="code", label="Code"
63 | ),
64 | ),
65 | ]
66 | ),
67 | )
68 | ],
69 | blank=True,
70 | ),
71 | ),
72 | ],
73 | options={"abstract": False,},
74 | bases=("wagtailcore.page",),
75 | ),
76 | ]
77 |
--------------------------------------------------------------------------------
/tests/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlipperPA/wagtailcodeblock/11aaf676b236c22bceac8c9cf778a1f55003ccb9/tests/migrations/__init__.py
--------------------------------------------------------------------------------
/tests/models.py:
--------------------------------------------------------------------------------
1 | from wagtail.admin.panels import FieldPanel
2 | from wagtail.blocks import StreamBlock
3 | from wagtail.fields import StreamField
4 | from wagtail.models import Page
5 | from wagtailcodeblock.blocks import CodeBlock
6 |
7 |
8 | class CodeStreamBlock(StreamBlock):
9 | """
10 | Test StreamBlock with a CodeBlock.
11 | """
12 |
13 | code = CodeBlock()
14 |
15 |
16 | class CodeBlockPage(Page):
17 | """
18 | Test Page with a code block in body.
19 | """
20 | body = StreamField([
21 | ('code', CodeBlock()),
22 | ], use_json_field=True)
23 |
24 | content_panels = Page.content_panels + [
25 | FieldPanel("body"),
26 | ]
27 |
--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 | WAGTAILADMIN_BASE_URL = "https://example.com"
4 | ALLOWED_HOSTS = ["*"]
5 | SECRET_KEY = "tests"
6 | DEBUG = True
7 | USE_TZ = True
8 |
9 | TEMPLATES = [
10 | {
11 | "BACKEND": "django.template.backends.django.DjangoTemplates",
12 | "APP_DIRS": True,
13 | "OPTIONS": {
14 | "context_processors": [
15 | "django.template.context_processors.debug",
16 | "django.template.context_processors.request",
17 | "django.contrib.auth.context_processors.auth",
18 | "django.contrib.messages.context_processors.messages",
19 | ],
20 | "debug": True,
21 | },
22 | }
23 | ]
24 |
25 | INSTALLED_APPS = settings.INSTALLED_APPS + [
26 | "django.contrib.auth",
27 | "django.contrib.contenttypes",
28 | "django.contrib.messages",
29 | "django.contrib.sessions",
30 | "django.contrib.staticfiles",
31 | "wagtail",
32 | "wagtail.admin",
33 | "wagtail.documents",
34 | "tests",
35 | "wagtail.images",
36 | "wagtail.users",
37 | "wagtailcodeblock",
38 | "modelcluster",
39 | "taggit",
40 | ]
41 |
42 | MIDDLEWARE = settings.MIDDLEWARE + [
43 | "django.middleware.security.SecurityMiddleware",
44 | "django.contrib.sessions.middleware.SessionMiddleware",
45 | "django.middleware.common.CommonMiddleware",
46 | "django.middleware.csrf.CsrfViewMiddleware",
47 | "django.contrib.auth.middleware.AuthenticationMiddleware",
48 | "django.contrib.messages.middleware.MessageMiddleware",
49 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
50 | ]
51 |
52 | ROOT_URLCONF = "tests.urls"
53 |
54 | DATABASES = {
55 | "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": "test_db.sqlite3"}
56 | }
57 |
58 | STATIC_URL = "/static/"
59 |
60 | WAGTAIL_SITE_NAME = "Test Site"
61 |
--------------------------------------------------------------------------------
/tests/test_blocks.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.django_db
5 | def test_create_page(test_page):
6 | """
7 | Tests creating a page with a Code Block.
8 | """
9 | assert (
10 | 'print([x for x in range(1, 5)])
'
11 | in test_page.body.render_as_block()
12 | )
13 |
--------------------------------------------------------------------------------
/tests/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import include, re_path
2 |
3 | from wagtail.admin import urls as wagtailadmin_urls
4 | from wagtail.documents import urls as wagtaildocs_urls
5 | from wagtail import urls as wagtail_urls
6 |
7 | urlpatterns = [
8 | re_path(r"^cms/", include(wagtailadmin_urls)),
9 | re_path(r"^documents/", include(wagtaildocs_urls)),
10 | re_path(r"", include(wagtail_urls)),
11 | ]
12 |
--------------------------------------------------------------------------------
/wagtailcodeblock/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlipperPA/wagtailcodeblock/11aaf676b236c22bceac8c9cf778a1f55003ccb9/wagtailcodeblock/__init__.py
--------------------------------------------------------------------------------
/wagtailcodeblock/blocks.py:
--------------------------------------------------------------------------------
1 | import wagtail
2 |
3 | from django.forms import Media
4 | from django.utils.functional import cached_property
5 | from django.utils.translation import gettext_lazy as _
6 |
7 | from wagtail.blocks import (
8 | StructBlock,
9 | TextBlock,
10 | ChoiceBlock,
11 | )
12 | from wagtail.blocks.struct_block import StructBlockAdapter
13 | from wagtail.telepath import register
14 |
15 | from .settings import get_language_choices
16 |
17 |
18 | class CodeBlock(StructBlock):
19 | """
20 | A Wagtail StreamField block for code syntax highlighting using PrismJS.
21 | """
22 |
23 | def __init__(self, local_blocks=None, **kwargs):
24 | # Languages included in PrismJS core
25 | # Review: https://github.com/PrismJS/prism/blob/gh-pages/prism.js#L602
26 | self.INCLUDED_LANGUAGES = (
27 | ("html", "HTML"),
28 | ("mathml", "MathML"),
29 | ("svg", "SVG"),
30 | ("xml", "XML"),
31 | )
32 |
33 | if local_blocks is None:
34 | local_blocks = []
35 | else:
36 | local_blocks = local_blocks.copy()
37 |
38 | language_choices, language_default = self.get_language_choice_list(**kwargs)
39 |
40 | local_blocks.extend(
41 | [
42 | (
43 | "language",
44 | ChoiceBlock(
45 | choices=language_choices,
46 | help_text=_("Coding language"),
47 | label=_("Language"),
48 | default=language_default,
49 | identifier="language",
50 | ),
51 | ),
52 | ("code", TextBlock(label=_("Code"), identifier="code")),
53 | ]
54 | )
55 |
56 | super().__init__(local_blocks, **kwargs)
57 |
58 | def get_language_choice_list(self, **kwargs):
59 | # Get default languages
60 | WCB_LANGUAGES = get_language_choices()
61 | # If a language is passed in as part of a code block, use it.
62 | language = kwargs.get("language", False)
63 |
64 | total_language_choices = WCB_LANGUAGES + self.INCLUDED_LANGUAGES
65 |
66 | if language in [lang[0] for lang in total_language_choices]:
67 | for language_choice in total_language_choices:
68 | if language_choice[0] == language:
69 | language_choices = (language_choice,)
70 | language_default = language_choice[0]
71 | else:
72 | language_choices = WCB_LANGUAGES
73 | language_default = kwargs.get("default_language")
74 |
75 | return language_choices, language_default
76 |
77 | class Meta:
78 | icon = "code"
79 | template = "wagtailcodeblock/code_block.html"
80 | form_classname = "code-block struct-block"
81 | form_template = "wagtailcodeblock/code_block_form.html"
82 |
83 |
84 | class CodeBlockAdapter(StructBlockAdapter):
85 | js_constructor = "wagtailcodeblock.blocks.CodeBlock"
86 |
87 | @cached_property
88 | def media(self):
89 | structblock_media = super().media
90 | return Media(
91 | js=structblock_media._js + ["wagtailcodeblock/js/wagtailcodeblock.js"],
92 | css=structblock_media._css,
93 | )
94 |
95 |
96 | register(CodeBlockAdapter(), CodeBlock)
97 |
--------------------------------------------------------------------------------
/wagtailcodeblock/settings.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 | PRISM_PREFIX = "//cdnjs.cloudflare.com/ajax/libs/prism/"
4 | PRISM_VERSION = "1.29.0"
5 |
6 |
7 | def get_language_choices():
8 | """
9 | Default list of language choices, if not overridden by Django.
10 | """
11 | DEFAULT_LANGUAGES = (
12 | ("bash", "Bash/Shell"),
13 | ("css", "CSS"),
14 | ("diff", "diff"),
15 | ("html", "HTML"),
16 | ("javascript", "Javascript"),
17 | ("json", "JSON"),
18 | ("python", "Python"),
19 | ("scss", "SCSS"),
20 | ("yaml", "YAML"),
21 | )
22 |
23 | return getattr(settings, "WAGTAIL_CODE_BLOCK_LANGUAGES", DEFAULT_LANGUAGES)
24 |
25 |
26 | def get_theme():
27 | """
28 | Returns a default theme, if not in the proejct's settings. Default theme is 'coy'.
29 | """
30 |
31 | return getattr(settings, "WAGTAIL_CODE_BLOCK_THEME", "coy")
32 |
33 |
34 | def get_line_numbers():
35 | """
36 | Returns the line numbers setting.
37 | """
38 |
39 | return getattr(settings, "WAGTAIL_CODE_BLOCK_LINE_NUMBERS", True)
40 |
41 |
42 | def get_copy_to_clipboard():
43 | """
44 | Returns the copy to clipboard setting.
45 | """
46 |
47 | return getattr(settings, "WAGTAIL_CODE_BLOCK_COPY_TO_CLIPBOARD", True)
48 |
--------------------------------------------------------------------------------
/wagtailcodeblock/static/wagtailcodeblock/css/wagtail-code-block.css:
--------------------------------------------------------------------------------
1 | .token.tag:before {
2 | content: none !important;
3 | }
4 |
5 | .token.tag {
6 | background-color: #fff !important;
7 | padding: 0 !important;
8 | }
9 |
10 | .code-block textarea {
11 | font-family: FreeMono, monospace;
12 | }
13 |
14 | .code-block code {
15 | display: block;
16 | }
17 |
--------------------------------------------------------------------------------
/wagtailcodeblock/static/wagtailcodeblock/css/wagtail-code-block.min.css:
--------------------------------------------------------------------------------
1 | .token.tag:before{content:none!important}.token.tag{background-color:#fff!important;padding:0!important}.code-block textarea {font-family:FreeMono,monospace;}.code-block code{display:block;}
--------------------------------------------------------------------------------
/wagtailcodeblock/static/wagtailcodeblock/js/wagtailcodeblock.js:
--------------------------------------------------------------------------------
1 | class CodeBlockDefinition extends window.wagtailStreamField.blocks
2 | .StructBlockDefinition {
3 | render(placeholder, prefix, initialState, initialError) {
4 | const block = super.render(
5 | placeholder,
6 | prefix,
7 | initialState,
8 | initialError,
9 | );
10 |
11 | var languageField = $(document).find('#' + prefix + '-language');
12 | var codeField = $(document).find('#' + prefix + '-code');
13 | var targetField = $(document).find('#' + prefix + '-target');
14 |
15 | function updateLanguage() {
16 | var languageCode = languageField.val();
17 | targetField.removeClass().addClass('language-' + languageCode);
18 | prismRepaint();
19 | }
20 |
21 | function prismRepaint() {
22 | Prism.highlightElement(targetField[0]);
23 | }
24 |
25 | function populateTargetCode() {
26 | var codeText = codeField.val();
27 | targetField.text(codeText);
28 | prismRepaint(targetField);
29 | }
30 |
31 | updateLanguage();
32 | populateTargetCode();
33 | languageField.on('change', updateLanguage);
34 | codeField.on('keyup', populateTargetCode);
35 |
36 | return block;
37 | }
38 | }
39 |
40 | window.telepath.register('wagtailcodeblock.blocks.CodeBlock', CodeBlockDefinition);
41 |
--------------------------------------------------------------------------------
/wagtailcodeblock/templates/wagtailcodeblock/code_block.html:
--------------------------------------------------------------------------------
1 | {% load static wagtailcodeblock_tags %}
2 | {% spaceless %}
3 | {% load_prism_css %}
4 | {% for key, val in self.items %}
5 | {% if key == "language" %}
6 |
48 | {% endif %}
49 | {% if key == "code" %}
50 |
51 | {{ val }}
52 |
53 |
59 | {% endif %}
60 | {% endfor %}
61 | {% endspaceless %}
62 |
--------------------------------------------------------------------------------
/wagtailcodeblock/templates/wagtailcodeblock/code_block_form.html:
--------------------------------------------------------------------------------
1 | {% load wagtailadmin_tags %}
2 |
3 |
35 |