From 98d7f0065817c8e562d3d9afeecb30563f286ba3 Mon Sep 17 00:00:00 2001 From: John Rayes Date: Sun, 24 Sep 2023 02:45:28 -0700 Subject: [PATCH] code tag changes - wrap code in preformatted html tags - do the same in the sceditor - bonus: fix php tag processing in the sceditor - php 8.3 changes highlight_string(), so update our integrations - do not add php start and end php tags to the final output of the php bbcode - add buttons for selecting and expanding code blocks with js; don't output them from php - remove smfSelectText( ) as it is unused now Signed-off-by: John Rayes --- Sources/BBCodeParser.php | 49 ++++--- Themes/default/css/index.css | 61 ++------- Themes/default/css/index_dark.css | 4 - Themes/default/css/index_light.css | 4 - .../default/css/jquery.sceditor.default.css | 17 +-- Themes/default/scripts/jquery.sceditor.smf.js | 43 ++++-- Themes/default/scripts/script.js | 123 ++++++------------ Themes/default/scripts/theme.js | 27 ---- 8 files changed, 115 insertions(+), 213 deletions(-) diff --git a/Sources/BBCodeParser.php b/Sources/BBCodeParser.php index 0e747a57a0..23fc28879c 100644 --- a/Sources/BBCodeParser.php +++ b/Sources/BBCodeParser.php @@ -357,14 +357,14 @@ class BBCodeParser [ 'tag' => 'code', 'type' => 'unparsed_content', - 'content' => '$1', + 'content' => '
{txt_code}
$1
', 'validate' => __CLASS__ . '::codeValidate', 'block_level' => true, ], [ 'tag' => 'code', 'type' => 'unparsed_equals_content', - 'content' => '$1', + 'content' => '
{txt_code} ($2)
$1
', 'validate' => __CLASS__ . '::codeValidate', 'block_level' => true, ], @@ -2226,16 +2226,20 @@ public static function highlightPhpCode(string $code): string // Remove special characters. $code = Utils::htmlspecialcharsDecode(strtr($code, ['
' => "\n", '
' => "\n", "\t" => 'SMF_TAB();', '[' => '['])); - $oldlevel = error_reporting(0); - - $buffer = str_replace(["\n", "\r"], '', @highlight_string($code, true)); - - error_reporting($oldlevel); + $patterns = ['/<\/?(?:pre|code)[^>]*>/']; + $replacements = ['']; // Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P. - $buffer = preg_replace('~SMF_TAB(?:<(?:font color|span style)="[^"]*?">)?\(\);~', '
' . "\t" . '
', $buffer); + $patterns[] = '/SMF_TAB(?:<\/(?:font|span)><(?:font color|span style)="[^"]*?">)?\(\);/'; + $replacements[] = "\t"; - return strtr($buffer, ['\'' => ''', '' => '', '' => '']); + // PHP 8.3 changed this to return real linebreaks, but SMF still expects HTML breaks. + if (version_compare(PHP_VERSION, '8.3', '>=')) { + $patterns[] = "/\n/"; + $replacements[] = '
'; + } + + return strtr(preg_replace($patterns, $replacements, highlight_string($code, true)), array('\'' => ''')); } /** @@ -2460,33 +2464,26 @@ public static function codeValidate(&$tag, &$data, $disabled, $params): void if (!isset($disabled['code'])) { $code = is_array($data) ? $data[0] : $data; - $php_parts = preg_split('~(<\?php|\?>)~', $code, -1, PREG_SPLIT_DELIM_CAPTURE); + $parts = preg_split('~(<\?php|\?>)~', $code, -1, PREG_SPLIT_DELIM_CAPTURE); - for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++) { + for ($i = 0, $n = count($parts); $i < $n; $i++) { // Do PHP code coloring? - if ($php_parts[$php_i] != '<?php') { + if ($parts[$i] != '<?php') { continue; } - $php_string = ''; - - while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?>') { - $php_string .= $php_parts[$php_i]; - $php_parts[$php_i++] = ''; + $string = ''; + while ($i + 1 < $n && $parts[$i] != '?>') { + $string .= $parts[$i]; + $parts[$i++] = ''; } - - $php_parts[$php_i] = self::highlightPhpCode($php_string . $php_parts[$php_i]); + $parts[$i] = self::highlightPhpCode($string . $parts[$i]); } - // Fix the PHP code stuff... - $code = str_replace("
\t
", "\t", implode('', $php_parts)); - - $code = str_replace("\t", "\t", $code); - if (is_array($data)) { - $data[0] = $code; + $data[0] = implode('', $parts); } else { - $data = $code; + $data = implode('', $parts); } } } diff --git a/Themes/default/css/index.css b/Themes/default/css/index.css index 68c170b117..7d0f922093 100644 --- a/Themes/default/css/index.css +++ b/Themes/default/css/index.css @@ -44,37 +44,11 @@ input, button, select, textarea { font-size: var(--ibst_font_size); line-height: var(--ibst_font_line_height); background: var(--ibst_bg_color); - outline: none; border: var(--ibst_border_width) var(--ibst_border_style) var(--ibst_border_color); vertical-align: middle; border-radius: var(--ibst_border_radius); - box-shadow: var(--ibst_box_shadow_color); padding: var(--ibst_padding); } -input:hover, textarea:hover, button:hover, select:hover { - outline: none; - border-color: var(--ibst_hover_border_color); -} -textarea:hover { - background: var(--ibst_textarea_hover_bg_color); -} -input:focus, textarea:focus, button:focus, select:focus { - outline: none; - border-color: var(--ibst_focus_border_color); - background: var(--ibst_focus_bg_color); -} -input, button, select { - padding: 0 0.4em; - height: 2em; - line-height: 2em; -} -select { - padding: 0.22em 0.2em; /* selects don't apply line-height */ -} -/* Selects with more than one line */ -select[size] { - height: auto; -} input[type="file"] { padding: 2px; height: auto; @@ -101,9 +75,6 @@ textarea { font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, monospace; } -.sceditor-container textarea, .sceditor-container textarea:focus { - box-shadow: none; -} #quick_edit_body_container textarea, .move_topic textarea, dd textarea { @@ -199,23 +170,6 @@ hr { background: var(--horizontal_rule_bg_color); box-shadow: var(--horizontal_rule_box_shadow); } -/* This is about links */ -a, a:visited { - color: var(--link); - text-decoration: none; - transition: ease 0.35s; -} -a:hover { - text-decoration: underline; - cursor: pointer; -} - -/* Help popups require a different styling of the body element. */ -/* Deprecated? */ -body#help_popup { - padding: 12px; -} - #likes li { clear: both; padding: 1px 0; @@ -290,6 +244,20 @@ a.new_posts:visited { .clear_right { clear: right; } +.reset { + all: unset; + outline: revert; +} +/* This is about links */ +a, a:visited, .reset.link { + color: var(--link); + text-decoration: none; + transition: ease 0.35s; +} +a:hover, .reset.link:hover { + text-decoration: underline; + cursor: pointer; +} /* Default font sizes: small (8pt), normal (10pt), and large (14pt). */ .smalltext, tr.smalltext th { @@ -387,7 +355,6 @@ blockquote cite::before { margin: 1px 0 6px 0; padding: 3px 12px; overflow: auto; - white-space: nowrap; max-height: 25em; } /* The "Quote:" and "Code:" header parts... */ diff --git a/Themes/default/css/index_dark.css b/Themes/default/css/index_dark.css index ddd1589207..ef302c57a2 100644 --- a/Themes/default/css/index_dark.css +++ b/Themes/default/css/index_dark.css @@ -326,13 +326,9 @@ --ibst_disabled_bg_color: hsl(0, 0%, 13%); --ibst_disabled_border_color: hsl(0, 0%, 71%); --ibst_disabled_txt_color: hsl(0, 0%, 60%); - --ibst_focus_bg_color: hsl(0, 0%, 0%); - --ibst_focus_border_color: hsl(207, 53%, 67%); --ibst_font_family: "Segoe UI", "Helvetica Neue", "Nimbus Sans L", Arial, "Liberation Sans", sans-serif; --ibst_font_size: 85%; --ibst_font_line_height: 150%; - --ibst_hover_border_color: hsl(207, 30%, 42%); - --ibst_textarea_hover_bg_color: hsl(0, 0%, 8%); --ibst_padding: 0.3em 0.4em; /* Maintenance Mode */ diff --git a/Themes/default/css/index_light.css b/Themes/default/css/index_light.css index dbd9818ec6..6a809fe687 100644 --- a/Themes/default/css/index_light.css +++ b/Themes/default/css/index_light.css @@ -324,13 +324,9 @@ --ibst_disabled_bg_color: hsl(0, 0%, 93%); --ibst_disabled_border_color: hsl(0, 0%, 71%); --ibst_disabled_txt_color: hsl(0, 0%, 60%); - --ibst_focus_bg_color: hsl(0, 0%, 100%); - --ibst_focus_border_color: hsl(207, 53%, 67%); --ibst_font_family: "Segoe UI", "Helvetica Neue", "Nimbus Sans L", Arial, "Liberation Sans", sans-serif; --ibst_font_size: 85%; --ibst_font_line_height: 150%; - --ibst_hover_border_color: hsl(207, 30%, 62%); - --ibst_textarea_hover_bg_color: hsl(0, 0%, 98%); --ibst_padding: 0.3em 0.4em; /* Maintenance Mode */ diff --git a/Themes/default/css/jquery.sceditor.default.css b/Themes/default/css/jquery.sceditor.default.css index e1f647b8eb..27ce633a69 100644 --- a/Themes/default/css/jquery.sceditor.default.css +++ b/Themes/default/css/jquery.sceditor.default.css @@ -1,5 +1,5 @@ /*! SCEditor | (C) 2011-2013, Sam Clarke | sceditor.com/license */ -html, p, code::before, table { +html, p, .bbc_code code::before, table { margin: 0; padding: 0; font-family: Verdana, Arial, Helvetica, sans-serif; @@ -43,30 +43,21 @@ table, td { min-width: 0.5ch; } -code::before { +.bbc_code code::before { position: absolute; content: 'Code:'; top: -1.35em; left: 0; } -code[data-title]::before { +.bbc_code code[data-title]::before { content: 'Code: (' attr(data-title) ')'; } -code { +.bbc_code { margin-top: 1.5em; position: relative; background: #eee; border: 1px solid #aaa; - white-space: pre; padding: .25em; - display: block; -} -.ie6 code, .ie7 code { - margin-top: 0; -} -code::before, code { - display: block; - text-align: left; } blockquote { diff --git a/Themes/default/scripts/jquery.sceditor.smf.js b/Themes/default/scripts/jquery.sceditor.smf.js index 0d2d554d63..38b7c90299 100644 --- a/Themes/default/scripts/jquery.sceditor.smf.js +++ b/Themes/default/scripts/jquery.sceditor.smf.js @@ -814,7 +814,17 @@ sceditor.formats.bbcode.set( sceditor.formats.bbcode.set( 'php', { + tags: { + code: { + class: 'php' + }, + span: { + class: 'phpcode' + } + }, + allowsEmpty: true, isInline: false, + allowedChildren: ['#', '#newline'], format: "[php]{0}[/php]", html: '{0}' } @@ -823,26 +833,41 @@ sceditor.formats.bbcode.set( sceditor.formats.bbcode.set( 'code', { tags: { - code: null + code: null, + div: { + class: 'codeheader' + }, + pre: { + class: 'bbc_code' + } }, isInline: false, allowedChildren: ['#', '#newline'], format: function (element, content) { - if ($(element).hasClass('php')) - return '[php]' + content.replace('[', '[') + '[/php]'; + let title = element.getAttribute('data-title'); + + if (element.className === 'php') + return content; + else if (element.tagName === 'DIV') + return ''; + else if (element.tagName === 'PRE') + return content; + else if (element.parentNode.tagName === 'PRE' && !title) + { + const t = element.parentNode.previousSibling.textContent; + + if (t.indexOf('(') != -1) + title = t.replace(/^[^(]+\(/, '').replace(/\)? \[.+/, ''); + } - var - dom = sceditor.dom, - attr = dom.attr, - title = attr(element, 'data-title'), - from = title ?' =' + title : ''; + const from = title ? ' =' + title : ''; return '[code' + from + ']' + content.replace('[', '[') + '[/code]'; }, html: function (element, attrs, content) { var from = attrs.defaultattr ? ' data-title="' + attrs.defaultattr + '"' : ''; - return '' + content.replace('[', '[') + '' + return '
' + content.replace('[', '[') + '
' } } ); diff --git a/Themes/default/scripts/script.js b/Themes/default/scripts/script.js index a0ea071e8b..84c6453e4d 100644 --- a/Themes/default/scripts/script.js +++ b/Themes/default/scripts/script.js @@ -1455,47 +1455,6 @@ function addLoadEvent(fNewOnload) aOnloadEvents[aOnloadEvents.length] = fNewOnload; } -// Get the text in a code tag. -function smfSelectText(oCurElement, bActOnElement) -{ - // The place we're looking for is one div up, and next door - if it's auto detect. - if (typeof(bActOnElement) == 'boolean' && bActOnElement) - var oCodeArea = document.getElementById(oCurElement); - else - var oCodeArea = oCurElement.parentNode.nextSibling; - - if (typeof(oCodeArea) != 'object' || oCodeArea == null) - return false; - - // Start off with my favourite, internet explorer. - if ('createTextRange' in document.body) - { - var oCurRange = document.body.createTextRange(); - oCurRange.moveToElementText(oCodeArea); - oCurRange.select(); - } - // Firefox at el. - else if (window.getSelection) - { - var oCurSelection = window.getSelection(); - // Safari is special! - if (oCurSelection.setBaseAndExtent) - { - oCurSelection.setBaseAndExtent(oCodeArea, 0, oCodeArea, oCodeArea.childNodes.length); - } - else - { - var curRange = document.createRange(); - curRange.selectNodeContents(oCodeArea); - - oCurSelection.removeAllRanges(); - oCurSelection.addRange(curRange); - } - } - - return false; -} - // A function used to clean the attachments on post page function cleanFileInput(idElement) { @@ -1742,40 +1701,6 @@ $(function() { return result; }); - // Generic event for smfSelectText() - $('.smf_select_text').on('click', function(e) { - e.preventDefault(); - - // Do you want to target yourself? - var actOnElement = $(this).attr('data-actonelement'); - - return typeof actOnElement !== "undefined" ? smfSelectText(actOnElement, true) : smfSelectText(this); - }); - - // Show the Expand bbc button if needed - $('.bbc_code').each(function(index, item) { - if($(item).css('max-height') == 'none') - return; - - if($(item).prop('scrollHeight') > parseInt($(item).css('max-height'), 10)) - $(item.previousSibling).find('.smf_expand_code').removeClass('hidden'); - }); - // Expand or Shrink the code bbc area - $('.smf_expand_code').on('click', function(e) { - e.preventDefault(); - - var oCodeArea = this.parentNode.nextSibling; - - if(oCodeArea.classList.contains('expand_code')) { - $(oCodeArea).removeClass('expand_code'); - $(this).html($(this).attr('data-expand-txt')); - } - else { - $(oCodeArea).addClass('expand_code'); - $(this).html($(this).attr('data-shrink-txt')); - } - }); - // Expand quotes if ((typeof(smf_quote_expand) != 'undefined') && (smf_quote_expand > 0)) { @@ -1827,6 +1752,8 @@ $(function() { }); }); } + + attachBbCodeEvents(document); }); function expand_quote_parent(oElement) @@ -1839,6 +1766,43 @@ function expand_quote_parent(oElement) }); } +function attachBbCodeEvents(parent) +{ + parent.querySelectorAll('.bbc_code').forEach(item => + { + const selectButton = document.createElement('button'); + selectButton.textContent = item.dataset.selectTxt; + selectButton.className = 'reset link'; + selectButton.addEventListener('click', function() + { + window.getSelection().selectAllChildren(item); + }); + item.previousSibling.append(' [', selectButton, ']'); + + // Show the Expand bbc button if needed + if (item.innerHeight < item.scrollHeight) + return; + + const expandButton = document.createElement('button'); + expandButton.textContent = item.dataset.expandTxt; + expandButton.className = 'reset link'; + expandButton.addEventListener('click', function() + { + if (item.classList.contains('expand_code')) + { + item.classList.remove('expand_code'); + this.textContent = item.dataset.expandTxt; + } + else + { + item.classList.add('expand_code'); + this.textContent = item.dataset.shrinkTxt; + } + }); + item.previousSibling.append(' [', expandButton, ']'); + }); +} + function avatar_fallback(e) { var e = window.e || e; var default_url = smf_avatars_url + '/default.png'; @@ -1963,14 +1927,7 @@ smc_preview_post.prototype.onDocSent = function (XMLDoc) bodyText += preview.getElementsByTagName('body')[0].childNodes[i].nodeValue; setInnerHTML(document.getElementById(this.opts.sPreviewBodyContainerID), bodyText); - $('#' + this.opts.sPreviewBodyContainerID + ' .smf_select_text').on('click', function(e) { - e.preventDefault(); - - // Do you want to target yourself? - var actOnElement = $(this).attr('data-actonelement'); - - return typeof actOnElement !== "undefined" ? smfSelectText(actOnElement, true) : smfSelectText(this); - }); + attachBbCodeEvents(document.getElementById(this.opts.sPreviewBodyContainerID)); document.getElementById(this.opts.sPreviewBodyContainerID).className = 'windowbg'; // Show a list of errors (if any). diff --git a/Themes/default/scripts/theme.js b/Themes/default/scripts/theme.js index 2bf3a241a1..d4e44c78a0 100755 --- a/Themes/default/scripts/theme.js +++ b/Themes/default/scripts/theme.js @@ -8,33 +8,6 @@ $(function() { $('a.bbc_link img').parent().css('border', '0'); }); -// The purpose of this code is to fix the height of overflow: auto blocks, because some browsers can't figure it out for themselves. -function smf_codeBoxFix() -{ - var codeFix = $('code'); - $.each(codeFix, function(index, tag) - { - if (is_webkit && $(tag).height() < 20) - $(tag).css({height: ($(tag).height() + 20) + 'px'}); - - else if (is_ff && ($(tag)[0].scrollWidth > $(tag).innerWidth() || $(tag).innerWidth() == 0)) - $(tag).css({overflow: 'scroll'}); - - // Holy conditional, Batman! - else if ( - 'currentStyle' in $(tag) && $(tag)[0].currentStyle.overflow == 'auto' - && ($(tag).innerHeight() == '' || $(tag).innerHeight() == 'auto') - && ($(tag)[0].scrollWidth > $(tag).innerWidth() || $(tag).innerWidth == 0) - && ($(tag).outerHeight() != 0) - ) - $(tag).css({height: ($(tag).height + 24) + 'px'}); - }); -} - -// Add a fix for code stuff? -if (is_ie || is_webkit || is_ff) - addLoadEvent(smf_codeBoxFix); - // Toggles the element height and width styles of an image. function smc_toggleImageDimensions() {