<NME> _page.twig
<BEF> {##
 # Standard HTML page template
 #
 # Dont't use, use "_page-<nav|no_nav|popup>.twig" instead!
 #
 # Blocks: page_nav:      Navigation path, like: "Dashboard", "Settings/Configuration", "Content/*", ...
 #         page_main:     content
 #         page_title:    used as html title
 #         page_subtitle: used as html title (optional)
 #         page_script:   script added after the body tag (script tags needed!)
 #}
{%- spaceless %}

{% set page_locale_long = app.locale %}
{% set page_locale_short = page_locale_long[:2] %}
{% set page_timezone_offset = app.timezone_offset %}

{% if block('page_subtitle') is defined %}
    {% set page_title = block('page_title') ~ ' » ' ~ block('page_subtitle') %}
{% else %}
    {% set page_title = block('page_title') %}
{% endif %}

{# TODO: Refactor this out. Too much business logic in the template. #}
{% set page_scripts_inc = [
    asset('js/lib.js', 'bolt')
] %}

{# Add library locale files #}
{% for lib in ['datepicker', 'moment', 'select2'] %}
    {% if app.filesystem.has("bolt_assets://js/locale/#{lib}/#{page_locale_long}.min.js") %}
        {% set page_scripts_inc = page_scripts_inc|merge([asset("js/locale/#{lib}/#{page_locale_long}.min.js", 'bolt')]) %}
    {% elseif page_locale_short != 'en' and app.filesystem.has("bolt_assets://js/locale/#{lib}/#{page_locale_short}.min.js") %}
        {% set page_scripts_inc = page_scripts_inc|merge([asset("js/locale/#{lib}/#{page_locale_short}.min.js", 'bolt')]) %}
    {% endif %}
{% endfor %}

{# Determine locale include for ckeditor #}
{% set page_locale_long_bcp47 = page_locale_long|replace({'_': '-'})|lower %}
{% if app.filesystem.has("bolt_assets://js/locale/ckeditor/#{page_locale_long_bcp47}.js") %}
    {% set ckeditor_lang = page_locale_long_bcp47 %}
{% else %}
    {% set ckeditor_lang = page_locale_short %}
{% endif %}

{% endspaceless -%}

<!DOCTYPE html>
<html lang="{{ page_locale_short }}">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">

    <title>{{ page_title|striptags|trim|raw }} – {{ config.get('general/branding/name') }}</title>

    <link rel="stylesheet" href="{{ asset('css/lib.css', 'bolt') }}">
    <link rel="stylesheet" href="{{ asset('css/bolt.css', 'bolt') }}">

    {% for script in page_scripts_inc -%}
        <script src="{{ script }}"></script>
    {% endfor %}

    <link rel="shortcut icon" href="{{ config.get('general/branding/favicon') ?: asset('img/favicon-bolt.ico', 'bolt') }}">
    <link rel="apple-touch-icon" sizes="57x57" href="{{ config.get('general/branding/apple-touch-icon') ?: asset('img/apple-touch-icon.png', 'bolt') }}">
    {% for size in ['72x72', '114x114', '144x144'] %}
        <link rel="apple-touch-icon" sizes="{{ size }}" href="{{ config.get('general/branding/apple-touch-icon-' ~ size) ?: asset('img/apple-touch-icon-' ~ size ~ '.png', 'bolt') }}">
    {% endfor %}
</head>

<body{% if page_bodyclass %} class="{{ page_bodyclass }}"{% endif %}>

    {% block page_plain %}{% endblock page_plain %}

{# Convert array of [assetPath, packageName] to actual url #}
{% set ckCss = [] %}
{% for assetData in config.get('general/wysiwyg/ck/contentsCss') %}
    {% set ckCss = ckCss|merge([asset(assetData.0, assetData.1)]) %}
{% endfor %}
{% set ckConfig = config.get('general/wysiwyg/ck')|default({})|merge({'contentsCss': ckCss}) %}

{% set bconfig = {
    locale: {
        short: page_locale_short,
        long: page_locale_long,
    },
    timezone: {
        offset: page_timezone_offset,
    },
    buid: buid(),
    uploadConfig: {
        url: path('files', {'namespace': app.request.get('namespace'), 'path': app.request.get('path')}),
        acceptFileTypes: app.config.get('general/accept_file_types'),
        maxSize: app['filepermissions'].getMaxUploadSize(),
        maxSizeNice: app['filepermissions'].getMaxUploadSizeNice(),
    },
    ckeditor: {
        lang:        ckeditor_lang,
        images:      0 + config.get('general/wysiwyg/images'),
        styles:      0 + config.get('general/wysiwyg/styles'),
        tables:      0 + config.get('general/wysiwyg/tables'),
        anchor:      0 + config.get('general/wysiwyg/anchor'),
        fontcolor:   0 + config.get('general/wysiwyg/fontcolor'),
        align:       0 + config.get('general/wysiwyg/align'),
        subsuper:    0 + config.get('general/wysiwyg/subsuper'),
        embed:       0 + config.get('general/wysiwyg/embed'),
        blockquote:  0 + config.get('general/wysiwyg/blockquote'),
        ruler:       0 + config.get('general/wysiwyg/ruler'),
        strike:      0 + config.get('general/wysiwyg/strike'),
        underline:   0 + config.get('general/wysiwyg/underline'),
        codesnippet: 0 + config.get('general/wysiwyg/codesnippet'),
        specialchar: 0 + config.get('general/wysiwyg/specialchar'),
        ck:          ckConfig,
        filebrowser: {
            browseUrl: path('recordbrowser'),
            browseUrl: path('recordbrowser'),
            imageBrowseUrl: path('files', {namespace: 'files'}),
        },
    },
    stackAddUrl: path('stack/add'),
    contentActionUrl: path('contentaction', global.request.query.all),
    google_api_key: config.get('general/google_api_key'),
} %}
<script src="{{ asset('js/bolt.js', 'bolt') }}" data-config="{{ bconfig|json_encode }}" data-jsdata="{{ app.jsdata|json_encode }}"></script>

{% block page_script %}
{% endblock page_script %}

</body>
</html>

<MSG> Optionally add undo, redo, cut, copy, paste buttons to toolbar in WYSIWYG

<DFF> @@ -110,6 +110,8 @@
         underline:   0 + config.get('general/wysiwyg/underline'),
         codesnippet: 0 + config.get('general/wysiwyg/codesnippet'),
         specialchar: 0 + config.get('general/wysiwyg/specialchar'),
+        clipboard:   0 + config.get('general/wysiwyg/clipboard'),
+        copypaste:   0 + config.get('general/wysiwyg/copypaste'),
         ck:          ckConfig,
         filebrowser: {
             browseUrl: path('recordbrowser'),

{'commit': "diff --git a/app/config/config.yml.dist b/app/config/config.yml.dist\nindex 064df60c9..040baea1a 100644\n--- a/app/config/config.yml.dist\n+++ b/app/config/config.yml.dist\n@@ -307,6 +307,8 @@ wysiwyg:\n     blockquote: false        # Allows the user to insert blockquotes using the `<blockquote>`-tag.\n     codesnippet: false       # Allows the user to insert code snippets using `<pre><code>`-tags.\n     specialchar: false       # Adds a button to insert special chars like '€' or '™'.\n+    clipboard: false         # Adds buttons to 'undo' and 'redo'.\n+    copypaste: false         # Adds buttons to 'cut', 'copy' and 'paste'.\n     ck:\n         autoParagraph: true  # If set to 'true', any pasted content is wrapped in `<p>`-tags for multiple line-breaks\n         disableNativeSpellChecker: true # If set to 'true' it will stop browsers from underlining spelling mistakes\ndiff --git a/app/src/js/modules/ckeditor.js b/app/src/js/modules/ckeditor.js\nindex 2fe2a8bdd..aa63771a6 100644\n--- a/app/src/js/modules/ckeditor.js\n+++ b/app/src/js/modules/ckeditor.js\n@@ -126,6 +126,13 @@\n             config.baseFloatZIndex = 100015;\n \n             config.toolbar = list(\n+                [                 {name: 'clipboard',   items: list( [set.clipboard,   'Undo'          ],\n+                                                                     [set.clipboard,   'Redo'          ],\n+                                                                     [set.copypaste,   'Cut'           ],\n+                                                                     [set.copypaste,   'Copy'          ],\n+                                                                     [set.copypaste,   'Paste'         ],\n+                                                                     [set.copypaste,   'PasteFromWord' ],\n+                                                                     [set.copypaste,   'PasteText'     ] )}],\n                 [                 {name: 'styles',      items: list( [                 'Format'        ],\n                                                                      [set.styles,      'Styles'        ] )}],\n \ndiff --git a/app/view/twig/_base/_page.twig b/app/view/twig/_base/_page.twig\nindex ff9d118a7..5300f67bf 100644\n--- a/app/view/twig/_base/_page.twig\n+++ b/app/view/twig/_base/_page.twig\n@@ -110,6 +110,8 @@\n         underline:   0 + config.get('general/wysiwyg/underline'),\n         codesnippet: 0 + config.get('general/wysiwyg/codesnippet'),\n         specialchar: 0 + config.get('general/wysiwyg/specialchar'),\n+        clipboard:   0 + config.get('general/wysiwyg/clipboard'),\n+        copypaste:   0 + config.get('general/wysiwyg/copypaste'),\n         ck:          ckConfig,\n         filebrowser: {\n             browseUrl: path('recordbrowser'),\n", 'commit_subject': 'Optionally add undo, redo, cut, copy, paste buttons to toolbar in WYSIWYG', 'repo_name': 'bolt/bolt', 'committer': {'name': 'Bob den Otter', 'email': 'd2d888a56640dcd57420997ce7da3dbc269fadfd@gmail.com', 'time_sec': '1522338385', 'tz_offset': '120', 'date': {'seconds': '1522338385'}}, 'commit_tree': 'cd14eb78af7ae32d294bb25e97bc16c8cdf2575d', 'commit_message': 'Optionally add undo, redo, cut, copy, paste buttons to toolbar in WYSIWYG\n', 'language_name': 'JavaScript', 'license': 'mit', 'hunks': "@@ -110,6 +110,8 @@\n         underline:   0 + config.get('general/wysiwyg/underline'),\n         codesnippet: 0 + config.get('general/wysiwyg/codesnippet'),\n         specialchar: 0 + config.get('general/wysiwyg/specialchar'),\n+        clipboard:   0 + config.get('general/wysiwyg/clipboard'),\n+        copypaste:   0 + config.get('general/wysiwyg/copypaste'),\n         ck:          ckConfig,\n         filebrowser: {\n             browseUrl: path('recordbrowser'),\n", 'addition_count': 2, 'deletion_count': 0, 'src_file': 'a/app/view/twig/_base/_page.twig', 'tgt_file': 'b/app/view/twig/_base/_page.twig', 'file_extension': '.twig', 'before_file': '{##\n # Standard HTML page template\n #\n # Dont\'t use, use "_page-<nav|no_nav|popup>.twig" instead!\n #\n # Blocks: page_nav:      Navigation path, like: "Dashboard", "Settings/Configuration", "Content/*", ...\n #         page_main:     content\n #         page_title:    used as html title\n #         page_subtitle: used as html title (optional)\n #         page_script:   script added after the body tag (script tags needed!)\n #}\n{%- spaceless %}\n\n{% set page_locale_long = app.locale %}\n{% set page_locale_short = page_locale_long[:2] %}\n{% set page_timezone_offset = app.timezone_offset %}\n\n{% if block(\'page_subtitle\') is defined %}\n    {% set page_title = block(\'page_title\') ~ \' » \' ~ block(\'page_subtitle\') %}\n{% else %}\n    {% set page_title = block(\'page_title\') %}\n{% endif %}\n\n{# TODO: Refactor this out. Too much business logic in the template. #}\n{% set page_scripts_inc = [\n    asset(\'js/lib.js\', \'bolt\')\n] %}\n\n{# Add library locale files #}\n{% for lib in [\'datepicker\', \'moment\', \'select2\'] %}\n    {% if app.filesystem.has("bolt_assets://js/locale/#{lib}/#{page_locale_long}.min.js") %}\n        {% set page_scripts_inc = page_scripts_inc|merge([asset("js/locale/#{lib}/#{page_locale_long}.min.js", \'bolt\')]) %}\n    {% elseif page_locale_short != \'en\' and app.filesystem.has("bolt_assets://js/locale/#{lib}/#{page_locale_short}.min.js") %}\n        {% set page_scripts_inc = page_scripts_inc|merge([asset("js/locale/#{lib}/#{page_locale_short}.min.js", \'bolt\')]) %}\n    {% endif %}\n{% endfor %}\n\n{# Determine locale include for ckeditor #}\n{% set page_locale_long_bcp47 = page_locale_long|replace({\'_\': \'-\'})|lower %}\n{% if app.filesystem.has("bolt_assets://js/locale/ckeditor/#{page_locale_long_bcp47}.js") %}\n    {% set ckeditor_lang = page_locale_long_bcp47 %}\n{% else %}\n    {% set ckeditor_lang = page_locale_short %}\n{% endif %}\n\n{% endspaceless -%}\n\n<!DOCTYPE html>\n<html lang="{{ page_locale_short }}">\n<head>\n    <meta http-equiv="X-UA-Compatible" content="IE=edge">\n    <meta charset="utf-8">\n    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">\n\n    <title>{{ page_title|striptags|trim|raw }} – {{ config.get(\'general/branding/name\') }}</title>\n\n    <link rel="stylesheet" href="{{ asset(\'css/lib.css\', \'bolt\') }}">\n    <link rel="stylesheet" href="{{ asset(\'css/bolt.css\', \'bolt\') }}">\n\n    {% for script in page_scripts_inc -%}\n        <script src="{{ script }}"></script>\n    {% endfor %}\n\n    <link rel="shortcut icon" href="{{ config.get(\'general/branding/favicon\') ?: asset(\'img/favicon-bolt.ico\', \'bolt\') }}">\n    <link rel="apple-touch-icon" sizes="57x57" href="{{ config.get(\'general/branding/apple-touch-icon\') ?: asset(\'img/apple-touch-icon.png\', \'bolt\') }}">\n    {% for size in [\'72x72\', \'114x114\', \'144x144\'] %}\n        <link rel="apple-touch-icon" sizes="{{ size }}" href="{{ config.get(\'general/branding/apple-touch-icon-\' ~ size) ?: asset(\'img/apple-touch-icon-\' ~ size ~ \'.png\', \'bolt\') }}">\n    {% endfor %}\n</head>\n\n<body{% if page_bodyclass %} class="{{ page_bodyclass }}"{% endif %}>\n\n    {% block page_plain %}{% endblock page_plain %}\n\n{# Convert array of [assetPath, packageName] to actual url #}\n{% set ckCss = [] %}\n{% for assetData in config.get(\'general/wysiwyg/ck/contentsCss\') %}\n    {% set ckCss = ckCss|merge([asset(assetData.0, assetData.1)]) %}\n{% endfor %}\n{% set ckConfig = config.get(\'general/wysiwyg/ck\')|default({})|merge({\'contentsCss\': ckCss}) %}\n\n{% set bconfig = {\n    locale: {\n        short: page_locale_short,\n        long: page_locale_long,\n    },\n    timezone: {\n        offset: page_timezone_offset,\n    },\n    buid: buid(),\n    uploadConfig: {\n        url: path(\'files\', {\'namespace\': app.request.get(\'namespace\'), \'path\': app.request.get(\'path\')}),\n        acceptFileTypes: app.config.get(\'general/accept_file_types\'),\n        maxSize: app[\'filepermissions\'].getMaxUploadSize(),\n        maxSizeNice: app[\'filepermissions\'].getMaxUploadSizeNice(),\n    },\n    ckeditor: {\n        lang:        ckeditor_lang,\n        images:      0 + config.get(\'general/wysiwyg/images\'),\n        styles:      0 + config.get(\'general/wysiwyg/styles\'),\n        tables:      0 + config.get(\'general/wysiwyg/tables\'),\n        anchor:      0 + config.get(\'general/wysiwyg/anchor\'),\n        fontcolor:   0 + config.get(\'general/wysiwyg/fontcolor\'),\n        align:       0 + config.get(\'general/wysiwyg/align\'),\n        subsuper:    0 + config.get(\'general/wysiwyg/subsuper\'),\n        embed:       0 + config.get(\'general/wysiwyg/embed\'),\n        blockquote:  0 + config.get(\'general/wysiwyg/blockquote\'),\n        ruler:       0 + config.get(\'general/wysiwyg/ruler\'),\n        strike:      0 + config.get(\'general/wysiwyg/strike\'),\n        underline:   0 + config.get(\'general/wysiwyg/underline\'),\n        codesnippet: 0 + config.get(\'general/wysiwyg/codesnippet\'),\n        specialchar: 0 + config.get(\'general/wysiwyg/specialchar\'),\n        ck:          ckConfig,\n        filebrowser: {\n            browseUrl: path(\'recordbrowser\'),\n            browseUrl: path(\'recordbrowser\'),\n            imageBrowseUrl: path(\'files\', {namespace: \'files\'}),\n        },\n    },\n    stackAddUrl: path(\'stack/add\'),\n    contentActionUrl: path(\'contentaction\', global.request.query.all),\n    google_api_key: config.get(\'general/google_api_key\'),\n} %}\n<script src="{{ asset(\'js/bolt.js\', \'bolt\') }}" data-config="{{ bconfig|json_encode }}" data-jsdata="{{ app.jsdata|json_encode }}"></script>\n\n{% block page_script %}\n{% endblock page_script %}\n\n</body>\n</html>\n', 'message': 'Optionally add undo, redo, cut, copy, paste buttons to toolbar in WYSIWYG\n', 'author_name': 'Bob den Otter', 'lang': 'twig'}