diff --git a/Languages/en_US/General.php b/Languages/en_US/General.php index 32e4927fd2e..acacdf5efac 100644 --- a/Languages/en_US/General.php +++ b/Languages/en_US/General.php @@ -239,6 +239,7 @@ $txt['page'] = 'Page'; $txt['prev'] = 'Previous page'; $txt['next'] = 'Next page'; +$txt['breadcrumb'] = 'Breadcrumb'; $txt['page_title_number'] = '{title} - Page {pagenum, number, integer}'; @@ -307,7 +308,8 @@ $txt['reply_quote'] = 'Reply with quote'; $txt['reply'] = 'Reply'; $txt['reply_noun'] = 'Reply'; -$txt['reply_number'] = 'Reply #{0, number} - '; +$txt['reply_number'] = 'Reply #{0, number}'; +$txt['reply_number_sr'] = 'Reply #{0, number}'; $txt['approve'] = 'Approve'; $txt['unapprove'] = 'Unapprove'; $txt['approve_all'] = 'approve all'; diff --git a/Languages/en_US/ManageMaintenance.php b/Languages/en_US/ManageMaintenance.php index 4cb01266061..6c0881900c1 100644 --- a/Languages/en_US/ManageMaintenance.php +++ b/Languages/en_US/ManageMaintenance.php @@ -100,7 +100,7 @@ $txt['database_optimize_attempt'] = 'Attempting to optimize your database...'; $txt['database_optimizing'] = 'Optimizing {0}... {1} KB optimized.'; $txt['database_already_optimized'] = 'All of the tables were already optimized.'; -$txt['database_opimize_unneeded'] = 'It was not necessary to optimize any tables.'; +$txt['database_opimize_unneeded'] = 'No tables need to be optimized.'; $txt['database_optimized'] = ' table(s) optimized.'; $txt['database_no_id'] = 'has a non-existent member ID'; @@ -162,16 +162,14 @@ $txt['maintain_recount'] = 'Recount all forum totals and statistics'; $txt['maintain_recount_info'] = 'Should the total replies of a topic or the number of PMs in your inbox be incorrect: this function will recount all saved counts and statistics for you.'; -$txt['maintain_errors'] = 'Find and repair any errors'; -$txt['maintain_errors_info'] = 'If, for example, posts or topics are missing after a server crash, this function may help in finding them again.'; -$txt['maintain_logs'] = 'Empty out unimportant logs'; -$txt['maintain_logs_info'] = 'This function will empty out all unimportant logs. This should be avoided unless something is wrong, but it does not hurt anything.'; -$txt['maintain_cache'] = 'Empty SMF’s cache'; -$txt['maintain_cache_info'] = 'This function will empty out the cache should you need it to be cleared.'; -$txt['maintain_optimize'] = 'Optimize all tables'; -$txt['maintain_optimize_info'] = 'This task allows you to optimize all tables. This will get rid of overhead, effectively making the tables smaller in size and your forum faster.'; +$txt['maintain_repair'] = 'Find and repair any errors'; +$txt['maintain_repair_info'] = 'Try to find and fix any errors that may prevent posts or topics from showinng up or being searchable. This should be run afer a forum conversion.'; +$txt['maintain_logs'] = 'Clear logs'; +$txt['maintain_logs_info'] = 'Clear out all information-related logs, such as the error log. This should be avoided unless something is wrong, and will not adversely affect forum operations'; +$txt['maintain_cleancache'] = 'Empty SMF’s cache'; +$txt['maintain_cleancache_info'] = 'Empty out the cache should you need it to be cleared.'; $txt['maintain_version'] = 'Check all files against current versions'; -$txt['maintain_version_info'] = 'This maintenance task allows you to do a detailed version check of all forum files against the official list of latest versions.'; +$txt['maintain_version_info'] = 'Runs a detailed version check of all forum files against the official list of latest versions and displays the results.'; $txt['maintain_rebuild_settings'] = 'Rebuild Settings.php'; $txt['maintain_rebuild_settings_info'] = 'This task reconstructs your Settings.php file. It does not change the values stored in the file. Instead, it cleans up and reformats your Settings.php file to a pristine version.'; $txt['maintain_run_now'] = 'Run task now'; @@ -183,7 +181,7 @@ $txt['maintain_old_nothing_else'] = 'Any sort of topic.'; $txt['maintain_old_are_moved'] = 'Moved/merged topic notices.'; $txt['maintain_old_are_locked'] = 'Locked.'; -$txt['maintain_old_are_not_stickied'] = 'But do not count stickied topics.'; +$txt['maintain_old_are_not_stickied'] = 'Exclude sticky topics.'; $txt['maintain_old_all'] = 'All boards (click to select specific boards)'; $txt['maintain_old_choose'] = 'Specific boards (click to select all)'; $txt['maintain_old_remove'] = 'Remove now'; @@ -207,10 +205,10 @@ $txt['mediumtext_introduction'] = 'The default messages table can contain posts up to a size of 65535 characters, in order be able to store bigger texts the column must be converted to "MEDIUMTEXT". This operation is not reversible.'; $txt['body_checking_introduction'] = 'This function will convert the column of your database that contains the text of the messages into a "TEXT" format (currently is "MEDIUMTEXT"). This operation will allow to slightly reduce the amount of space occupied by each message (1 byte per message). If any message stored into the database is longer than 65535 characters it will be truncated and part of the text will be lost.'; -$txt['entity_convert_title'] = 'Convert HTML-entities to UTF-8 characters'; -$txt['entity_convert_only_utf8'] = 'The database needs to be in UTF-8 format before HTML-entities can be converted to UTF-8'; -$txt['entity_convert_introduction'] = 'This function will convert all characters that are stored in the database as HTML-entities to UTF-8 characters. This is especially useful when you have just converted your forum from a character set like ISO-8859-1 while non-latin characters were used on the forum. The browser then sends all characters as HTML-entities. For example, the HTML-entity &#945; represents the greek letter α (alpha). Converting entities to UTF-8 will improve searching and sorting of text and reduce storage size.'; -$txt['entity_convert_proceed'] = 'Proceed'; +$txt['maintain_convertentities'] = 'Convert HTML-entities to UTF-8 characters'; +$txt['maintain_convertentities_only_utf8'] = 'The database needs to be in UTF-8 format before HTML-entities can be converted to UTF-8'; +$txt['maintain_convertentities_info'] = 'This function will convert all characters that are stored in the database as HTML-entities to UTF-8 characters. This is especially useful when you have just converted your forum from a character set like ISO-8859-1 while non-latin characters were used on the forum. The browser then sends all characters as HTML-entities. For example, the HTML-entity &#945; represents the greek letter α (alpha). Converting entities to UTF-8 will improve searching and sorting of text and reduce storage size.'; +$txt['maintain_convertentities_proceed'] = 'Proceed'; // Move topics out. $txt['move_topics_maintenance'] = 'Move Topics'; diff --git a/Sources/Actions/Admin/Maintenance.php b/Sources/Actions/Admin/Maintenance.php index 089aba225f9..f8023c9ba61 100644 --- a/Sources/Actions/Admin/Maintenance.php +++ b/Sources/Actions/Admin/Maintenance.php @@ -77,7 +77,6 @@ class Maintenance implements ActionInterface public static array $subactions = [ 'routine' => [ 'function' => 'routine', - 'template' => 'maintain_routine', 'activities' => [ 'version' => 'version', 'repair' => 'repair', @@ -89,7 +88,6 @@ class Maintenance implements ActionInterface ], 'database' => [ 'function' => 'database', - 'template' => 'maintain_database', 'activities' => [ 'optimize' => 'optimize', 'convertentities' => 'entitiesToUnicode', @@ -153,7 +151,7 @@ public function execute(): void // Set a few things. Utils::$context['page_title'] = Lang::getTxt('maintain_title', file: 'Admin'); Utils::$context['sub_action'] = $this->subaction; - Utils::$context['sub_template'] = !empty(self::$subactions[$this->subaction]['template']) ? self::$subactions[$this->subaction]['template'] : ''; + Utils::$context['sub_template'] = self::$subactions[$this->subaction]['template'] ?? 'options'; $call = \is_string(self::$subactions[$this->subaction]['function']) && method_exists($this, self::$subactions[$this->subaction]['function']) ? [$this, self::$subactions[$this->subaction]['function']] : Utils::getCallable(self::$subactions[$this->subaction]['function']); @@ -182,6 +180,12 @@ public function routine(): void if (isset($_GET['done']) && \in_array($_GET['done'], ['recount', 'rebuild_settings'])) { Utils::$context['maintenance_finished'] = Lang::getTxt('maintain_' . $_GET['done'], file: 'ManageMaintenance'); } + Utils::$context['template_layers'][] = 'maintain'; + Utils::$context['options'] = array_combine( + array_keys(self::$subactions[$this->subaction]['activities']), + array_fill(0, \count(self::$subactions[$this->subaction]['activities']), []), + ); + Utils::$context['post_url'] = Config::$scripturl . '?action=admin;area=maintain'; } /** @@ -189,23 +193,30 @@ public function routine(): void */ public function database(): void { + Utils::$context['template_layers'][] = 'maintain'; + Utils::$context['options'] = array_combine( + array_keys(self::$subactions[$this->subaction]['activities']), + array_fill(0, \count(self::$subactions[$this->subaction]['activities']), []), + ); + Utils::$context['post_url'] = Config::$scripturl . '?action=admin;area=maintain;sa=database'; + // Show some conversion options? Utils::$context['convert_entities'] = true; if (Config::$db_type == 'mysql') { - $colData = Db::$db->list_columns('{db_prefix}messages', true); + $body_type = array_column(Db::$db->list_columns('{db_prefix}messages', true), 'type', 'name')['body']; + Utils::$context['options']['convertmsgbody']['title'] = Lang::getTxt(($body_type == 'text' ? 'mediumtext' : 'text') . '_title', file: 'ManageMaintenance'); + Utils::$context['options']['convertmsgbody']['info'] = Lang::getTxt('mediumtext_info', file: 'ManageMaintenance'); - foreach ($colData as $column) { - if ($column['name'] == 'body') { - $body_type = $column['type']; - } + if ($body_type != 'text' && !empty(Config::$modSettings['max_messageLength']) && Config::$modSettings['max_messageLength'] < 65536) { + Utils::$context['options']['convertmsgbody']['after'] = '

' . Lang::getTxt('convert_to_suggest_text', file: 'ManageMaintenance') . '

'; } - - Utils::$context['convert_to'] = $body_type == 'text' ? 'mediumtext' : null; + } else { + unset(Utils::$context['options']['convertmsgbody']); } if (isset($_GET['done']) && $_GET['done'] == 'convertentities') { - Utils::$context['maintenance_finished'] = Lang::getTxt('entity_convert_title', file: 'ManageMaintenance'); + Utils::$context['maintenance_finished'] = Lang::getTxt('maintain_convertentities_title', file: 'ManageMaintenance'); } } diff --git a/Sources/Actions/Admin/Subscriptions.php b/Sources/Actions/Admin/Subscriptions.php index c1de93addea..c33cb384f31 100644 --- a/Sources/Actions/Admin/Subscriptions.php +++ b/Sources/Actions/Admin/Subscriptions.php @@ -870,6 +870,7 @@ public function modifyUser(): void // Setup the template. Utils::$context['sub_template'] = 'modify_user_subscription'; Utils::$context['page_title'] = Lang::getTxt(Utils::$context['action_type'] . '_subscriber', file: 'ManagePaid'); + Theme::loadJavaScriptFile('paidsubs.js', ['defer' => true, 'minimize' => true], 'smf_paidsubs'); // If we haven't been passed the subscription ID get it. if (Utils::$context['log_id'] && !Utils::$context['sub_id']) { diff --git a/Sources/Actions/Admin/Themes.php b/Sources/Actions/Admin/Themes.php index 886fe6e5ffb..afd2a233709 100644 --- a/Sources/Actions/Admin/Themes.php +++ b/Sources/Actions/Admin/Themes.php @@ -630,7 +630,7 @@ public function setOptions(): void $available_modes[$mode] = Lang::getTxt('colormode_' . $mode, file: 'Themes') ?? $mode; } - Utils::$context['options'][] = Lang::getTxt('theme_opt_colormode', file: 'Themes'); + Utils::$context['options'][] = Lang::getTxt('theme_opt_colormode', file: 'Profile'); Utils::$context['options'][] = [ 'id' => 'theme_colormode', 'label' => Lang::getTxt('theme_pick_colormode', file: 'Themes'), @@ -875,6 +875,9 @@ public function setSettings(): void if (!empty(Theme::$current->settings['theme_variants'])) { Utils::$context['theme_variants'] = []; + // Add the default variant + Theme::$current->settings['theme_variants'] = array_unique(array_merge(['default'], Theme::$current->settings['theme_variants'])); + foreach (Theme::$current->settings['theme_variants'] as $variant) { // Have any text, old chap? Utils::$context['theme_variants'][$variant] = [ diff --git a/Sources/Actions/BoardIndex.php b/Sources/Actions/BoardIndex.php index 638398fa2c6..28f2bf63889 100644 --- a/Sources/Actions/BoardIndex.php +++ b/Sources/Actions/BoardIndex.php @@ -57,8 +57,11 @@ class BoardIndex implements ActionInterface, Routable public function execute(): void { Theme::loadTemplate('BoardIndex'); - Utils::$context['template_layers'][] = 'boardindex_outer'; - + Utils::$context['sub_templates'] = [ + 'newsfader', + 'boardindex', + 'info_center', + ]; Utils::$context['page_title'] = Lang::getTxt('forum_index', ['forum_name' => Utils::$context['forum_name']], file: 'General'); // Set a canonical URL for this page. diff --git a/Sources/Actions/Calendar.php b/Sources/Actions/Calendar.php index 1cc1de000d7..32a4a545e34 100644 --- a/Sources/Actions/Calendar.php +++ b/Sources/Actions/Calendar.php @@ -118,7 +118,7 @@ public function show(): void // This is gonna be needed... Theme::loadTemplate('Calendar'); - Theme::loadCSSFile('calendar.css', ['force_current' => false, 'validate' => true, 'rtl' => 'calendar.rtl.css'], 'smf_calendar'); + Theme::loadCSSFile('calendar.css', ['force_current' => false, 'validate' => true], 'smf_calendar'); Theme::loadJavaScriptFile('calendar.js', ['defer' => true], 'smf_calendar'); // Did they specify an individual event ID? If so, let's splice the year/month in to what we would otherwise be doing. @@ -771,6 +771,8 @@ public function clock(): void $bcd = !isset($_REQUEST['rb']) && !isset($_REQUEST['omfg']) && !isset($_REQUEST['time']); Theme::loadTemplate('Calendar'); + Theme::loadCSSFile('calendar.css', ['force_current' => false, 'validate' => true], 'smf_calendar'); + Theme::loadJavaScriptFile('calendar.js', ['defer' => true], 'smf_calendar'); if ($bcd) { Utils::$context['sub_template'] = 'bcd'; diff --git a/Sources/Actions/Display.php b/Sources/Actions/Display.php index ec029abade4..fea003f1bb1 100644 --- a/Sources/Actions/Display.php +++ b/Sources/Actions/Display.php @@ -209,7 +209,6 @@ public function prepareDisplayContext(): array|bool 'quote' => [ 'label' => Lang::getTxt('quote_action', file: 'General'), 'href' => Config::$scripturl . '?action=post;quote=' . $output['id'] . ';topic=' . Utils::$context['current_topic'] . '.' . Utils::$context['start'] . ';last_msg=' . Topic::$info->id_last_msg, - 'javascript' => 'onclick="return oQuickReply.quote(' . $output['id'] . ');"', 'icon' => 'quote', 'show' => Utils::$context['can_quote'], ], @@ -225,6 +224,7 @@ public function prepareDisplayContext(): array|bool 'label' => Lang::getTxt('quick_edit', file: 'General'), 'class' => 'quick_edit', 'id' => 'modify_button_' . $output['id'], + 'custom' => 'hidden', 'icon' => 'quick_edit_button', 'show' => $output['can_modify'], ], @@ -1004,6 +1004,7 @@ protected function setupTemplate(): void { // Load the proper template. Theme::loadTemplate('Display'); + Theme::loadCSSFile('postbit.css', ['minimize' => true], 'smf_post'); Theme::loadCSSFile('attachments.css', ['minimize' => true, 'order_pos' => 450], 'smf_attachments'); // Set a canonical URL for this page. @@ -1077,7 +1078,7 @@ protected function setupTemplate(): void // Load the drafts js file. if (!empty(Topic::$info->permissions['drafts_autosave'])) { - Theme::loadJavaScriptFile('drafts.js', ['defer' => false, 'minimize' => true], 'smf_drafts'); + Theme::loadJavaScriptFile('sceditor.plugins.drafts.js', ['defer' => true, 'minimize' => true], 'smf_drafts'); } // Spellcheck @@ -1085,16 +1086,15 @@ protected function setupTemplate(): void Theme::loadJavaScriptFile('spellcheck.js', ['defer' => false, 'minimize' => true], 'smf_spellcheck'); } - // topic.js Theme::loadJavaScriptFile('topic.js', ['defer' => true, 'minimize' => true], 'smf_topic'); - - // quotedText.js + Theme::loadJavaScriptFile('sceditor.plugins.quote-fast.js', ['defer' => true, 'minimize' => true], 'smf_quote_fast'); Theme::loadJavaScriptFile('quotedText.js', ['defer' => true, 'minimize' => true], 'smf_quotedText'); // Mentions if (!empty(Config::$modSettings['enable_mentions']) && User::$me->allowedTo('mention')) { - Theme::loadJavaScriptFile('jquery.atwho.min.js', ['defer' => true], 'smf_atwho'); - Theme::loadJavaScriptFile('jquery.caret.min.js', ['defer' => true], 'smf_caret'); + Theme::loadJavaScriptFile('caret.js', ['defer' => true, 'minimize' => true], 'smf_caret'); + Theme::loadCSSFile('atwho.css', ['minimize' => true], 'smf_atwho'); + Theme::loadJavaScriptFile('atwho.js', ['defer' => true, 'minimize' => true], 'smf_atwho'); Theme::loadJavaScriptFile('mentions.js', ['defer' => true, 'minimize' => true], 'smf_mentions'); } @@ -1300,8 +1300,7 @@ protected function setOldTopicWarning(): void */ protected function loadEditor(): void { - // Now create the editor. - new Editor([ + $editorOptions = [ 'id' => 'quickReply', 'value' => '', 'labels' => [ @@ -1309,9 +1308,37 @@ protected function loadEditor(): void ], // We do HTML preview here. 'preview_type' => Editor::PREVIEW_HTML, - // This is required + // This is a required field. 'required' => true, - ]); + // SCEditor plugins. + 'plugins' => ['quoteFast'], + // SCEditor options. + 'options' => [ + 'quoteFastOptions' => [ + 'sPostContainerId' => 'forumposts', + 'sQuickButtonsSelector' => '.quickbuttons', + 'iChildNum' => 0, + 'sJumpAnchor' => 'quickreply_anchor', + ], + ], + ]; + + if (!empty(Config::$modSettings['enable_mentions']) && User::$me->allowedTo('mention')) { + $editorOptions['plugins'][] = 'mentions'; + } + + if (!empty(Topic::$info->permissions['drafts_autosave'])) { + $editorOptions['plugins'][] = 'drafts'; + $editorOptions['plugins'][] = 'messageDrafts'; + $editorOptions['options']['draftOptions'] = [ + 'sLastNote' => 'draft_lastautosave', + 'sLastID' => 'id_draft', + 'sQueryParams' => 'action=post2;board=' . Utils::$context['current_board'], + 'iFreq' => empty(Config::$modSettings['drafts_autosave_frequency']) ? 60000 : Config::$modSettings['drafts_autosave_frequency'] * 1000, + ]; + } + + new Editor($editorOptions); Utils::$context['attached'] = ''; Utils::$context['make_poll'] = isset($_REQUEST['poll']); diff --git a/Sources/Actions/Login.php b/Sources/Actions/Login.php index 1104f1fddac..b8136c9db42 100644 --- a/Sources/Actions/Login.php +++ b/Sources/Actions/Login.php @@ -60,6 +60,7 @@ public function execute(): void parent::checkAjax(); // Get the template ready.... not really much else to do. + Theme::loadJavaScriptFile('login.js', ['minimize' => true], 'smf_login'); Utils::$context['page_title'] = Lang::getTxt('login', file: 'General'); Utils::$context['default_username'] = &$_REQUEST['u']; Utils::$context['default_password'] = ''; diff --git a/Sources/Actions/Login2.php b/Sources/Actions/Login2.php index d8e7cb8d58e..4592bb8128f 100644 --- a/Sources/Actions/Login2.php +++ b/Sources/Actions/Login2.php @@ -237,6 +237,7 @@ public function main(): void // Load the template stuff. Theme::loadTemplate('Login'); + Theme::loadJavaScriptFile('login.js', ['minimize' => true], 'smf_login'); Utils::$context['sub_template'] = 'login'; // Create a one time token. diff --git a/Sources/Actions/MessageIndex.php b/Sources/Actions/MessageIndex.php index de8e6a62972..142338c662f 100644 --- a/Sources/Actions/MessageIndex.php +++ b/Sources/Actions/MessageIndex.php @@ -258,7 +258,7 @@ public static function getBoardList(array $boardListOptions = []): array public static function buildTopicContext(array $row): void { // Reference the main color class. - $colorClass = 'windowbg'; + $colorClass = ''; // Does the theme support message previews? if (!empty(Config::$modSettings['preview_characters'])) { @@ -311,9 +311,14 @@ public static function buildTopicContext(array $row): void // Decide how many pages the topic should have. if ($row['num_replies'] + 1 > Utils::$context['messages_per_page']) { + $templateOverrides = [ + 'extra_before' => '', + 'current_page' => '', + ]; + // We can't pass start by reference. $start = -1; - $pages = new PageIndex(Config::$scripturl . '?topic=' . $row['id_topic'] . '.%1$d', $start, $row['num_replies'] + 1, (int) Utils::$context['messages_per_page'], true, false); + $pages = new PageIndex(Config::$scripturl . '?topic=' . $row['id_topic'] . '.%1$d', $start, $row['num_replies'] + 1, (int) Utils::$context['messages_per_page'], true, false, $templateOverrides); // If we can use all, show all. if (!empty(Config::$modSettings['enableAllMessages']) && $row['num_replies'] + 1 < Config::$modSettings['enableAllMessages']) { @@ -350,7 +355,7 @@ public static function buildTopicContext(array $row): void // Is this topic pending approval, or does it have any posts pending approval? if (!empty($row['unapproved_posts']) && User::$me->allowedTo('approve_posts')) { - $colorClass .= (!$row['approved'] ? ' approvetopic' : ' approvepost'); + $colorClass .= !$row['approved'] ? ' approvetopic' : ' approvepost'; } // Sticky topics should get a different color, too. @@ -933,15 +938,11 @@ protected function setUnapprovedPostsMessage(): void { // If we can view unapproved messages and there are some build up a list. if (User::$me->allowedTo('approve_posts') && (Board::$info->unapproved_topics || Board::$info->unapproved_posts)) { - $untopics = Board::$info->unapproved_topics ? '' . Board::$info->unapproved_topics . '' : 0; - - $unposts = Board::$info->unapproved_posts ? '' . (Board::$info->unapproved_posts - Board::$info->unapproved_topics) . '' : 0; - Utils::$context['unapproved_posts_message'] = Lang::getTxt( 'there_are_unapproved_topics', [ - 'topics' => $untopics, - 'posts' => $unposts, + 'topics' => Board::$info->unapproved_topics, + 'posts' => Board::$info->unapproved_posts - Board::$info->unapproved_topics, 'url' => Config::$scripturl . '?action=moderate;area=postmod;sa=' . (Board::$info->unapproved_topics ? 'topics' : 'posts') . ';brd=' . Board::$info->id, ], file: 'General', @@ -958,6 +959,7 @@ protected function setUnapprovedPostsMessage(): void */ protected function setupTemplate(): void { + Theme::loadTemplate('BoardIndex'); Theme::loadTemplate('MessageIndex'); // Javascript for inline editing. diff --git a/Sources/Actions/PersonalMessage.php b/Sources/Actions/PersonalMessage.php index 8d522fb593c..c56357c234a 100644 --- a/Sources/Actions/PersonalMessage.php +++ b/Sources/Actions/PersonalMessage.php @@ -291,6 +291,7 @@ public function isAgreementAction(): bool public function execute(): void { Theme::loadTemplate(isset($_REQUEST['xml']) ? 'Xml' : 'PersonalMessage'); + Theme::loadCSSFile('postbit.css', ['minimize' => true], 'smf_post'); $this->buildLimitBar(); diff --git a/Sources/Actions/Post.php b/Sources/Actions/Post.php index bd5dbd0910d..ea7a7d6ef92 100644 --- a/Sources/Actions/Post.php +++ b/Sources/Actions/Post.php @@ -348,17 +348,20 @@ public function show(): void // Mentions if (!empty(Config::$modSettings['enable_mentions']) && User::$me->allowedTo('mention')) { - Theme::loadJavaScriptFile('jquery.caret.min.js', ['defer' => true], 'smf_caret'); - Theme::loadJavaScriptFile('jquery.atwho.min.js', ['defer' => true], 'smf_atwho'); + Theme::loadCSSFile('atwho.css', ['minimize' => true], 'smf_atwho'); + Theme::loadJavaScriptFile('atwho.js', ['dmfer' => true, 'minimize' => true], 'smf_atwho'); + Theme::loadJavaScriptFile('caret.js', ['defer' => true, 'minimize' => true], 'smf_caret'); Theme::loadJavaScriptFile('mentions.js', ['defer' => true, 'minimize' => true], 'smf_mentions'); } // Load the drafts.js file if (Utils::$context['drafts_autosave']) { - Theme::loadJavaScriptFile('drafts.js', ['defer' => false, 'minimize' => true], 'smf_drafts'); + Theme::loadJavaScriptFile('sceditor.plugins.drafts.js', ['defer' => true, 'minimize' => true], 'smf_drafts'); } - // quotedText.js + Theme::loadCSSFile('attachments.css', ['minimize' => true, 'order_pos' => 450], 'smf_attachments'); + Theme::loadJavaScriptFile('post.js', ['defer' => true, 'minimize' => true], 'smf_post'); + Theme::loadJavaScriptFile('sceditor.plugins.quote-fast.js', ['defer' => true, 'minimize' => true], 'smf_quote_fast'); Theme::loadJavaScriptFile('quotedText.js', ['defer' => true, 'minimize' => true], 'smf_quotedText'); // Knowing the current board ID might be handy. @@ -1001,8 +1004,6 @@ protected function showPreview(): void 'is_last' => false, ]; } - Utils::$context['last_choice_id'] = $choice_id; - Utils::$context['choices'][\count(Utils::$context['choices']) - 1]['is_last'] = true; } // Are you... a guest? @@ -1600,50 +1601,6 @@ protected function showAttachments(): void });'); } } - - // File Upload. - if (Utils::$context['can_post_attachment']) { - $acceptedFiles = empty(Utils::$context['allowed_extensions']) ? '' : implode(',', array_map( - function ($val) { - return !empty($val) ? ('.' . Utils::htmlTrim($val)) : ''; - }, - explode(',', Utils::$context['allowed_extensions']), - )); - - Theme::loadJavaScriptFile('dropzone.min.js', ['defer' => true], 'smf_dropzone'); - Theme::loadJavaScriptFile('smf_fileUpload.js', ['defer' => true, 'minimize' => true], 'smf_fileUpload'); - Theme::addInlineJavaScript(' - $(function() { - smf_fileUpload({ - dictDefaultMessage : ' . Utils::escapeJavaScript(Lang::getTxt('attach_drop_zone', file: 'Post')) . ', - dictFallbackMessage : ' . Utils::escapeJavaScript(Lang::getTxt('attach_drop_zone_no', file: 'Post')) . ', - dictCancelUpload : ' . Utils::escapeJavaScript(Lang::getTxt('modify_cancel', file: 'General')) . ', - genericError: ' . Utils::escapeJavaScript(Lang::getTxt('attach_php_error', file: 'Post')) . ', - text_attachDropzoneLabel: ' . Utils::escapeJavaScript(Lang::getTxt('attach_drop_zone', file: 'Post')) . ', - text_attachLimitNag: ' . Utils::escapeJavaScript(Lang::getTxt('attach_limit_nag', file: 'Post')) . ', - text_attachLeft: ' . Utils::escapeJavaScript(Lang::getTxt('attachments_left', file: 'Post')) . ', - text_deleteAttach: ' . Utils::escapeJavaScript(Lang::getTxt('attached_file_delete', file: 'Post')) . ', - text_attachDeleted: ' . Utils::escapeJavaScript(Lang::getTxt('attached_file_deleted', file: 'Post')) . ', - text_insertBBC: ' . Utils::escapeJavaScript(Lang::getTxt('attached_insert_bbc', file: 'Post')) . ', - text_attachUploaded: ' . Utils::escapeJavaScript(Lang::getTxt('attached_file_uploaded', file: 'Post')) . ', - text_attach_unlimited: ' . Utils::escapeJavaScript(Lang::getTxt('attach_drop_unlimited', file: 'Post')) . ', - text_totalMaxSize: ' . Utils::escapeJavaScript(Lang::getTxt('attach_max_total_file_size_current', file: 'Post')) . ', - text_max_size_progress: ' . Utils::escapeJavaScript('{currentRemain} ' . (Config::$modSettings['attachmentPostLimit'] >= 1024 ? Lang::getTxt('megabyte', file: 'General') : Lang::getTxt('kilobyte', file: 'General')) . ' / {currentTotal} ' . (Config::$modSettings['attachmentPostLimit'] >= 1024 ? Lang::getTxt('megabyte', file: 'General') : Lang::getTxt('kilobyte', file: 'General'))) . ', - dictMaxFilesExceeded: ' . Utils::escapeJavaScript(Lang::getTxt('more_attachments_error', file: 'Post')) . ', - dictInvalidFileType: ' . Utils::escapeJavaScript(Lang::getTxt('cant_upload_type', Utils::$context, file: 'Post')) . ', - dictFileTooBig: ' . Utils::escapeJavaScript(Lang::getTxt('file_too_big', [Lang::numberFormat(Config::$modSettings['attachmentSizeLimit'], 0)], file: 'Post')) . ', - acceptedFiles: ' . Utils::escapeJavaScript($acceptedFiles) . ', - thumbnailWidth: ' . (!empty(Config::$modSettings['attachmentThumbWidth']) ? Config::$modSettings['attachmentThumbWidth'] : 'null') . ', - thumbnailHeight: ' . (!empty(Config::$modSettings['attachmentThumbHeight']) ? Config::$modSettings['attachmentThumbHeight'] : 'null') . ', - limitMultiFileUploadSize:' . round(max(Config::$modSettings['attachmentPostLimit'] - (Utils::$context['attachments']['total_size'] / 1024), 0)) * 1024 . ', - maxFileAmount: ' . (!empty(Utils::$context['num_allowed_attachments']) ? Utils::$context['num_allowed_attachments'] : 'null') . ', - maxTotalSize: ' . (!empty(Config::$modSettings['attachmentPostLimit']) ? Config::$modSettings['attachmentPostLimit'] : '0') . ', - maxFilesize: ' . (!empty(Config::$modSettings['attachmentSizeLimit']) ? Config::$modSettings['attachmentSizeLimit'] : '0') . ', - }); - });', true); - } - - Theme::loadCSSFile('attachments.css', ['minimize' => true, 'order_pos' => 450], 'smf_attachments'); } /** @@ -1764,7 +1721,7 @@ protected function loadDrafts(): void */ protected function loadEditor(): void { - new Editor([ + $editorOptions = [ 'id' => 'message', 'value' => Utils::$context['message'], 'labels' => [ @@ -1772,11 +1729,85 @@ protected function loadEditor(): void ], // We do XML preview here. 'preview_type' => Editor::PREVIEW_XML, + // This is a required field. 'required' => true, + // SCEditor plugins. + 'plugins' => ['quoteFast', 'messagePreview'], + // SCEditor options. 'options' => [ 'autofocus' => $this->intent !== self::INTENT_NEW_TOPIC, + 'quoteFastOptions' => [ + 'sPostContainerId' => 'recent', + 'sQuickButtonsSelector' => '.quickbuttons', + 'iChildNum' => 1, + 'sJumpAnchor' => 'postmodify', + ], ], - ]); + ]; + + if (!empty(Config::$modSettings['enable_mentions']) && User::$me->allowedTo('mention')) { + $editorOptions['plugins'][] = 'mentions'; + } + + if (Utils::$context['drafts_autosave']) { + $editorOptions['plugins'][] = 'drafts'; + $editorOptions['plugins'][] = 'messageDrafts'; + $editorOptions['options']['draftOptions'] = [ + 'sLastNote' => 'draft_lastautosave', + 'sLastID' => 'id_draft', + 'sQueryParams' => 'action=post2;board=' . Utils::$context['current_board'], + 'iFreq' => empty(Config::$modSettings['drafts_autosave_frequency']) ? 60000 : Config::$modSettings['drafts_autosave_frequency'] * 1000, + ]; + } + + if (Utils::$context['can_post_attachment']) { + Theme::loadJavaScriptFile('dropzone.min.js', ['defer' => true], 'smf_dropzone'); + Theme::loadJavaScriptFile('smf_fileUpload.js', ['defer' => true, 'minimize' => true], 'smf_fileUpload'); + + $editorOptions['plugins'][] = 'fileUpload'; + $editorOptions['options']['fileUploadOptions'] = [ + 'dictDefaultMessage' => Lang::getTxt('attach_drop_zone', file: 'Post'), + 'dictFallbackMessage' => Lang::getTxt('attach_drop_zone_no', file: 'Post'), + 'dictCancelUpload' => Lang::getTxt('modify_cancel', file: 'Post'), + 'genericError' => Lang::getTxt('attach_php_error', file: 'Post'), + 'text_attachDropzoneLabel' => Lang::getTxt('attach_drop_zone', file: 'Post'), + 'text_attachLimitNag' => Lang::getTxt('attach_limit_nag', file: 'Post'), + 'text_attachLeft' => Lang::getTxt('attachments_left', file: 'Post'), + 'text_deleteAttach' => Lang::getTxt('attached_file_delete', file: 'Post'), + 'text_attachDeleted' => Lang::getTxt('attached_file_deleted', file: 'Post'), + 'text_insertBBC' => Lang::getTxt('attached_insert_bbc', file: 'Post'), + 'text_attachUploaded' => Lang::getTxt('attached_file_uploaded', file: 'Post'), + 'text_attach_unlimited' => Lang::getTxt('attach_drop_unlimited', file: 'Post'), + 'text_totalMaxSize' => Lang::getTxt('attach_max_total_file_size_current', file: 'Post'), + 'text_max_size_progress' => '{currentRemain} ' . (Config::$modSettings['attachmentPostLimit'] >= 1024 ? Lang::getTxt('megabyte', file: 'General') : Lang::getTxt('kilobyte', file: 'General')) . ' / {currentTotal} ' . (Config::$modSettings['attachmentPostLimit'] >= 1024 ? Lang::getTxt('megabyte', file: 'General') : Lang::getTxt('kilobyte', file: 'General')), + 'dictMaxFilesExceeded' => Lang::getTxt('more_attachments_error', file: 'Post'), + 'dictInvalidFileType' => Lang::getTxt('cant_upload_type', Utils::$context), + 'dictFileTooBig' => Lang::getTxt( + 'file_too_big', + [Lang::numberFormat(Config::$modSettings['attachmentSizeLimit'], 0)], + ), + 'acceptedFiles' => implode( + ',', + array_map( + fn($val) => !empty($val) ? ('.' . Utils::htmlTrim($val)) : '', + explode(',', Utils::$context['allowed_extensions']), + ), + ), + 'thumbnailWidth' => !empty(Config::$modSettings['attachmentThumbWidth']) ? Config::$modSettings['attachmentThumbWidth'] : null, + 'thumbnailHeight' => !empty(Config::$modSettings['attachmentThumbHeight']) ? Config::$modSettings['attachmentThumbHeight'] : null, + 'limitMultiFileUploadSize' => round( + max( + Config::$modSettings['attachmentPostLimit'] - (Utils::$context['attachments']['total_size'] / 1024), + 0, + ), + ) * 1024, + 'maxFileAmount' => !empty(Utils::$context['num_allowed_attachments']) ? Utils::$context['num_allowed_attachments'] : null, + 'maxTotalSize' => !empty(Config::$modSettings['attachmentPostLimit']) ? Config::$modSettings['attachmentPostLimit'] : 0, + 'maxFilesize' => !empty(Config::$modSettings['attachmentSizeLimit']) ? Config::$modSettings['attachmentSizeLimit'] : 0, + ]; + } + + new Editor($editorOptions); } /** @@ -1830,6 +1861,100 @@ protected function setMessageIcons(): void */ protected function setupPostingFields(): void { + /* + **Keys in Each Option Array**: + + **`can_show`** (bool) + A boolean flag that determines if the option should be displayed. If + set to `true`, the option will be rendered; if `false`, it will be skipped. + + **`name`** (string) + The `name` attribute for the checkbox input. This is the identifier + sent when the form is submitted. Each checkbox must have a unique name. + + **`id`** (string) + The `id` attribute for the checkbox input. This must be unique + and is used to associate the checkbox with its label (`for` attribute). + + **`checked`** (bool) + Whether the checkbox should be checked by default. If set to `true`, + the checkbox will be checked. If set to `false`, it will be unchecked. + + **`label`** (string) + The label text that will appear next to the checkbox. It describes + the option to the user, such as "Notify me of replies". + + **`value`** (string, optional) + The `value` attribute of the checkbox. This determines the value that + will be submitted when the checkbox is checked. The default value is `'1'`. + + **`hidden`** (array, optional) + An associative array of hidden input fields related to this option. + The keys represent the name of the hidden field, and the values + represent the value of the hidden field. + */ + Utils::$context['Additional_options'] = [ + [ + 'can_show' => Utils::$context['can_notify'], + 'name' => 'notify', + 'id' => 'check_notify', + 'checked' => Utils::$context['notify'] || !empty(Theme::$current->options['auto_notify']) || Utils::$context['auto_notify'], + 'label' => Lang::getTxt('notify_replies', file: 'Post'), + ], + [ + 'can_show' => Utils::$context['can_lock'], + 'name' => 'lock', + 'id' => 'check_lock', + 'checked' => Utils::$context['locked'], + 'label' => Lang::getTxt('lock_topic', file: 'Post'), + 'hidden' => ['already_locked' => Utils::$context['already_locked']], + ], + [ + 'can_show' => Utils::$context['can_sticky'], + 'name' => 'sticky', + 'id' => 'check_sticky', + 'checked' => Utils::$context['sticky'], + 'label' => Lang::getTxt('sticky_after_posting', file: 'Post'), + 'hidden' => ['already_sticky' => Utils::$context['already_sticky']], + ], + [ + 'can_show' => Utils::$context['can_move'], + 'name' => 'move', + 'id' => 'check_move', + 'checked' => !empty(Utils::$context['move']), + 'label' => Lang::getTxt('move_after_posting', file: 'Post'), + ], + [ + 'can_show' => Utils::$context['can_announce'] && Utils::$context['is_first_post'], + 'name' => 'announce_topic', + 'id' => 'check_announce', + 'checked' => !empty(Utils::$context['announce']), + 'label' => Lang::getTxt('announce_topic', file: 'Post'), + ], + [ + 'can_show' => Utils::$context['show_approval'] === 2, + 'name' => 'approve', + 'id' => 'approve', + 'checked' => Utils::$context['show_approval'] === 2, + 'label' => Lang::getTxt('approve_this_post', file: 'Post'), + ], + [ + 'can_show' => true, + 'name' => 'goback', + 'id' => 'check_back', + 'checked' => Utils::$context['back_to_topic'] || !empty(Theme::$current->options['return_to_post']), + 'label' => Lang::getTxt('back_to_topic', file: 'Post'), + ], + [ + 'can_show' => true, + 'name' => 'ns', + 'id' => 'check_smileys', + 'checked' => !Utils::$context['use_smileys'], + 'label' => Lang::getTxt('dont_use_smileys', file: 'Post'), + 'value' => 'NS', + ], + ]; + /* Each item in Utils::$context['posting_fields'] is an array similar to one of the following: diff --git a/Sources/Actions/Profile/BuddyIgnoreLists.php b/Sources/Actions/Profile/BuddyIgnoreLists.php index 76a73fb46fd..2c9b66cd15f 100644 --- a/Sources/Actions/Profile/BuddyIgnoreLists.php +++ b/Sources/Actions/Profile/BuddyIgnoreLists.php @@ -96,7 +96,7 @@ public function execute(): void Menu::$loaded['profile']->tab_data = [ 'title' => Lang::getTxt('editBuddyIgnoreLists', file: 'Profile'), 'description' => Lang::getTxt('buddy_ignore_desc', file: 'Profile'), - 'icon_class' => 'main_icons profile_hd', + 'icon_class' => 'main_icons profile medium_icon', 'tabs' => [ 'buddies' => [], 'ignore' => [], diff --git a/Sources/Actions/Profile/Main.php b/Sources/Actions/Profile/Main.php index b46a9fcc50a..76b9109a69b 100644 --- a/Sources/Actions/Profile/Main.php +++ b/Sources/Actions/Profile/Main.php @@ -460,7 +460,7 @@ class Main implements ActionInterface, Routable 'report' => [ 'label' => 'report_profile', 'custom_url' => '{scripturl}?action=reporttm;{session_var}={session_id}', - 'icon' => 'warning', + 'icon' => 'report', 'enabled' => true, 'permission' => [ 'own' => [], @@ -671,12 +671,12 @@ public function execute(): void } // Set the template for this area and add the profile layer. + Theme::loadTemplate('Profile'); + Theme::loadJavaScriptFile('profile.js', ['defer' => true, 'minimize' => true], 'smf_profile'); + Theme::loadCSSFile('profile.css', ['minimize' => true], 'smf_profile'); Utils::$context['sub_template'] = $menu->include_data['sub_template'] ?? $menu->include_data['function']; - Utils::$context['template_layers'][] = 'profile'; - Theme::loadJavaScriptFile('profile.js', ['defer' => false, 'minimize' => true], 'smf_profile'); - // Right - are we saving - if so let's save the old data first. if (Utils::$context['completed_save']) { // Clean up the POST variables. diff --git a/Sources/Actions/Profile/ShowPosts.php b/Sources/Actions/Profile/ShowPosts.php index 962f30209c4..e820442da0e 100644 --- a/Sources/Actions/Profile/ShowPosts.php +++ b/Sources/Actions/Profile/ShowPosts.php @@ -90,7 +90,7 @@ public function execute(): void Menu::$loaded['profile']->tab_data = [ 'title' => Lang::getTxt('showPosts', file: 'Profile'), 'description' => Lang::getTxt('showPosts_help', file: 'Profile'), - 'icon_class' => 'main_icons profile_hd', + 'icon_class' => 'main_icons profile medium_icon', 'tabs' => [ 'messages' => [ ], diff --git a/Sources/Actions/Profile/StatPanel.php b/Sources/Actions/Profile/StatPanel.php index 61f428c1975..12281406b27 100644 --- a/Sources/Actions/Profile/StatPanel.php +++ b/Sources/Actions/Profile/StatPanel.php @@ -49,7 +49,7 @@ public function execute(): void // Menu tab Menu::$loaded['profile']->tab_data = [ 'title' => Lang::getTxt('statPanel_showStats', ['name' => Profile::$member->name]), - 'icon' => 'stats_info.png', + 'icon_class' => 'main_icons stats', ]; // Is the load average too high to allow searching just now? diff --git a/Sources/Actions/Profile/Summary.php b/Sources/Actions/Profile/Summary.php index 1016576f04b..d95cfb20ca4 100644 --- a/Sources/Actions/Profile/Summary.php +++ b/Sources/Actions/Profile/Summary.php @@ -48,7 +48,7 @@ public function execute(): void // Menu tab Menu::$loaded['profile']->tab_data = [ 'title' => Lang::getTxt('summary', file: 'General'), - 'icon_class' => 'main_icons profile_hd', + 'icon_class' => 'main_icons profile medium_icon', ]; // Expand the warning settings. diff --git a/Sources/Actions/Profile/Tracking.php b/Sources/Actions/Profile/Tracking.php index 56821e3b265..d698b1882b4 100644 --- a/Sources/Actions/Profile/Tracking.php +++ b/Sources/Actions/Profile/Tracking.php @@ -95,7 +95,7 @@ public function execute(): void Menu::$loaded['profile']->tab_data = [ 'title' => Lang::getTxt('tracking', file: 'Profile'), 'description' => Lang::getTxt('tracking_description', file: 'Profile'), - 'icon_class' => 'main_icons profile_hd', + 'icon_class' => 'main_icons profile medium_icon', 'tabs' => [], ]; diff --git a/Sources/Actions/Reminder.php b/Sources/Actions/Reminder.php index d79772522da..0348bd5f7d6 100644 --- a/Sources/Actions/Reminder.php +++ b/Sources/Actions/Reminder.php @@ -261,6 +261,7 @@ public function setPassword2(): void IntegrationHook::call('integrate_reset_pass', [$this->member->username, $this->member->username, $_POST['passwrd1']]); Theme::loadTemplate('Login'); + Theme::loadJavaScriptFile('login.js', ['minimize' => true], 'smf_login'); Utils::$context += [ 'page_title' => Lang::getTxt('reminder_password_set', file: 'Profile'), 'sub_template' => 'login', @@ -384,6 +385,7 @@ public function secretAnswer2(): void // Tell them it went fine. Theme::loadTemplate('Login'); + Theme::loadJavaScriptFile('login.js', ['minimize' => true], 'smf_login'); Utils::$context += [ 'page_title' => Lang::getTxt('reminder_password_set', file: 'Profile'), 'sub_template' => 'login', diff --git a/Sources/BBCode/Code1.php b/Sources/BBCode/Code1.php index ce2a6fcd102..4787b73e2fb 100644 --- a/Sources/BBCode/Code1.php +++ b/Sources/BBCode/Code1.php @@ -39,7 +39,7 @@ class Code1 extends BBCode /** * */ - public ?string $content = '
{txt_code} {txt_code_select}
$1'; + public ?string $content = '
{txt_code}
$1
'; /** * @@ -63,53 +63,27 @@ public function validate(BBCodeInterface &$bbc, array|string &$data, array $disa if (!isset($disabled['code'])) { $code = \is_array($data) ? $data[0] : $data; - $add_begin = ( - \is_array($data) - && isset($data[1]) - && strtoupper($data[1]) === 'PHP' - && !str_contains($code, '<?php') - ); + $parts = preg_split('~(<\?php|\?>)~', $code, -1, PREG_SPLIT_DELIM_CAPTURE); - if ($add_begin) { - $code = '<?php ' . $code . '?>'; - $data[1] = 'PHP'; - } - - $php_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++] = ''; - } - - $php_parts[$php_i] = Parser::highlightPhpCode($php_string . $php_parts[$php_i]); + $string = ''; - if (\is_array($data) && empty($data[1])) { - $data[1] = 'PHP'; + while ($i + 1 < $n && $parts[$i] != '?>') { + $string .= $parts[$i]; + $parts[$i++] = ''; } - } - - // Fix the PHP code stuff... - $code = str_replace("
\t
", "\t", implode('', $php_parts)); - - $code = str_replace("\t", "\t", $code); - - if ($add_begin) { - $code = preg_replace(['/^(.+?)<\?.{0,40}?php(?: |\s)/', '/\?>((?:\s*<\/(font|span)>)*)$/m'], '$1', $code, 2); + $parts[$i] = Parser::highlightPhpCode($string . $parts[$i]); } if (\is_array($data)) { - $data[0] = $code; + $data[0] = implode('', $parts); } else { - $data = $code; + $data = implode('', $parts); } } } diff --git a/Sources/BBCode/Code2.php b/Sources/BBCode/Code2.php index bbf35ab00cf..c331d0a4c9c 100644 --- a/Sources/BBCode/Code2.php +++ b/Sources/BBCode/Code2.php @@ -39,7 +39,7 @@ class Code2 extends BBCode /** * */ - public ?string $content = '
{txt_code} ($2) {txt_code_select}
$1'; + public ?string $content = '
{txt_code} ($2)
$1
'; /** * @@ -63,53 +63,27 @@ public function validate(BBCodeInterface &$bbc, array|string &$data, array $disa if (!isset($disabled['code'])) { $code = \is_array($data) ? $data[0] : $data; - $add_begin = ( - \is_array($data) - && isset($data[1]) - && strtoupper($data[1]) === 'PHP' - && !str_contains($code, '<?php') - ); + $parts = preg_split('~(<\?php|\?>)~', $code, -1, PREG_SPLIT_DELIM_CAPTURE); - if ($add_begin) { - $code = '<?php ' . $code . '?>'; - $data[1] = 'PHP'; - } - - $php_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++] = ''; - } - - $php_parts[$php_i] = Parser::highlightPhpCode($php_string . $php_parts[$php_i]); + $string = ''; - if (\is_array($data) && empty($data[1])) { - $data[1] = 'PHP'; + while ($i + 1 < $n && $parts[$i] != '?>') { + $string .= $parts[$i]; + $parts[$i++] = ''; } - } - - // Fix the PHP code stuff... - $code = str_replace("
\t
", "\t", implode('', $php_parts)); - - $code = str_replace("\t", "\t", $code); - - if ($add_begin) { - $code = preg_replace(['/^(.+?)<\?.{0,40}?php(?: |\s)/', '/\?>((?:\s*<\/(font|span)>)*)$/m'], '$1', $code, 2); + $parts[$i] = Parser::highlightPhpCode($string . $parts[$i]); } if (\is_array($data)) { - $data[0] = $code; + $data[0] = implode('', $parts); } else { - $data = $code; + $data = implode('', $parts); } } } diff --git a/Sources/Editor.php b/Sources/Editor.php index 610f834200a..5267870f362 100644 --- a/Sources/Editor.php +++ b/Sources/Editor.php @@ -134,9 +134,13 @@ class Editor implements \ArrayAccess, \Stringable /** * Determines whether the editor starts in rich text (WYSIWYG) mode. - * Set based on global config, user preferences, or explicit options. * - * @var bool True for WYSIWYG mode, false for source mode. + * This property is initialized based on several factors: + * - If the global setting `disable_wysiwyg` is enabled; + * - If the user's theme preference or the provided option `force_rich` is true; + * - If a request explicitly sets the editor mode for the instance (e.g., `$_REQUEST[$this->id . '_mode']`); it overrides other settings. + * + * @var bool True if the editor starts in WYSIWYG mode, false otherwise. */ public bool $rich_active; @@ -295,14 +299,74 @@ class Editor implements \ArrayAccess, \Stringable ****************/ /** - * Construct and configure a new Editor instance. + * Initializes a new instance of the editor class and configures its options and behavior. + * + * This constructor prepares the editor with default or user-specified options, including its + * dimensions, behavior, and visual features. It also sets up toolbars, smileys, and WYSIWYG + * capabilities if enabled. + * + * Behavior: + * 1. Initializes the editor with a unique ID and sets default options for its dimensions and behavior. + * 2. Configures the smiley and BBC toolbars, applying any necessary translations or replacements. + * 3. Enables WYSIWYG mode based on global settings, user preferences, or provided options. + * 4. Sets the SCEditor options using the provided `$options` array. + * 5. Adds backward compatibility support by storing the editor ID in the global context. + * + * Supported options: + * - `id` (string): The unique identifier for the editor instance. Defaults to 'message'. + * - `value` (string): The initial value of the editor, with certain replacements for compatibility. + * - `disable_smiley_box` (bool): Whether to disable the smiley selection box. Default is false. + * - `columns` (int): Number of columns for the editor text area. Default is 60. + * - `rows` (int): Number of rows for the editor text area. Default is 18. + * - `width` (string): Width of the editor. Default is '100%'. + * - `height` (string): Height of the editor. Default is '250px'. + * - `form` (string): The form name associated with the editor. Default is 'postmodify'. + * - `preview_type` (int): The type of preview for the editor. Default is `self::PREVIEW_HTML`. + * - `labels` (array): Additional labels for customization. + * - `required` (bool): Indicates whether the editor input is required. Default is false. + * - `force_rich` (bool): Force the editor to start in rich text mode. Default is false. + * This option directly influences `$this->rich_active`, which determines if WYSIWYG mode is enabled. + * - `plugins` (array): List of additional plugins to be loaded. Defaults to an empty array if not set. + * - `disable_url_autolinking` (bool): If set, disables the autolinker plugin for URLs. + * - `options` (array): Additional SCEditor configuration options to be merged with default settings. + * + * Custom SCEditor options: + * - `commandsWithDropdown`: Identifies buttons that use dropdown menus. + * - `textOnlyCommands`: Configures buttons to display text without icons. + * - `commandsWithText`: Configures buttons to show text alongside icons. + * + * Hooks: + * - Hook: `integrate_sceditor_options` + * - Parameters: + * - `array &$this->sce_options`: Reference to the array of SCEditor options. + * + * - Hook: `integrate_bbc_buttons` + * - Parameters: + * - `array &$bbc_tags`: Reference to the array of BBC tags. + * - `array &$editor_tag_map`: Reference to the mapping of BBC tags to SCEditor commands. + * - `array &$disabled_tags`: Reference to the array of disabled BBC tags. * - * Initializes editor properties, toolbars, smileys, WYSIWYG support, and - * SCEditor options. + * Example bbc tag array: + * ```php + * [ + * 'code' => 'b', + * 'description' => Lang::getTxt('bold', var: 'editortxt'), // Optional + * 'image' => 'bold', // Optional + * 'before' => '[b]', // Optional + * 'after' => '[/b]', // Optional + * ] + * ``` * - * Applies default values and merges user-supplied $options where provided. + * Example editor tag map: + * ```php + * [ + * 'bbcode' => 'sceditorCommand', + * ] + * ``` * - * Sets up integration points for mods via hooks. + * Notes: + * - A blank array (`[]`) in the `bbc_tags` represents a separator between groups of buttons in the toolbar. + * - The `editor_tag_map` is only used when the BBC tag and the SCEditor command differ. * * @param array $options An associative array of configuration options for * the editor. @@ -533,7 +597,7 @@ public static function getMessageIcons(int $board_id): array $icons[$row['filename']] = [ 'value' => $row['filename'], 'name' => $row['title'], - 'url' => ($icon_exists ? $images_url : $default_images_url) . '/post/' . $row['filename'] . '.png', + 'url' => Theme::$current->settings[file_exists(Theme::$current->settings['theme_dir'] . '/images/post/' . $row['filename'] . '.png') ? 'images_url' : 'default_images_url'] . '/post/' . $row['filename'] . '.png', ]; } Db::$db->free_result($request); @@ -583,7 +647,7 @@ protected function init(): void Theme::loadCSSFile('jquery.sceditor.theme.css', ['force_current' => true, 'validate' => true], 'smf_jquery_sceditor_theme'); Theme::loadJavaScriptFile('jquery.sceditor.bbcode.min.js', [], 'smf_sceditor_bbcode'); - Theme::loadJavaScriptFile('sceditor.plugins.smf.js', ['minimize' => true], 'smf_sceditor_smf'); + Theme::loadJavaScriptFile('sceditor.plugins.smf.js', ['minimize' => true], 'smf_sceditor_smf_plugin'); $locale_key = Lang::getTxt('lang_dictionary', file: 'General'); @@ -609,7 +673,6 @@ protected function init(): void $sc_extra_langs = 'sceditor.locale["' . $locale_key . '"] = ' . json_encode($translation_map, JSON_UNESCAPED_UNICODE) . ';'; Theme::addInlineJavaScript($sc_extra_langs, true); - Theme::addInlineJavaScript(' var smf_smileys_url = \'' . Theme::$current->settings['smileys_url'] . '\'; var bbc_quote_from = \'' . addcslashes(Lang::getTxt('quote_from', file: 'General'), "'") . '\'; @@ -666,11 +729,19 @@ protected function buildButtons(): void } /** - * Initializes and constructs the BBC button toolbar for the editor. + * Initializes and constructs the BBC (Bulletin Board Code) button toolbar for the editor. * - * Sets up available BBC tags, disables tags as required, applies integration hooks, - * and builds the toolbar structure for rendering. + * This method sets up the available BBC tags and their corresponding actions for the editor. + * It manages which tags are enabled, disabled, and how they appear in the toolbar. The method + * also allows integrations or modifications via hooks for custom functionality. * + * Behavior: + * 1. Links key context variables (e.g., `bbc_tags`, `disabled_tags`, `bbc_toolbar`) for use in the editor. + * 2. Initializes the BBC tags with predefined options, such as the tag's code, description, and icon. + * 3. Maps specific BBC tags to SCEditor commands for seamless functionality. + * 4. Dynamically generates a list of disabled buttons based on configuration settings. + * 5. Applies integration hooks (`integrate_bbc_buttons`) to allow modifications to BBC buttons. + * 6. Assembles the toolbar structure based on the active and disabled tags. */ protected function buildBbcToolbar(): void { @@ -683,6 +754,7 @@ protected function buildBbcToolbar(): void Utils::$context['bbc_toolbar'] = &self::$bbc_toolbar; Utils::$context['bbcodes_handlers'] = &self::$bbc_handlers; + // Map BBC tags to SCEditor commands. $editor_tag_map = [ 'b' => 'bold', 'i' => 'italic', @@ -710,6 +782,17 @@ protected function buildBbcToolbar(): void } } + // Allow mods to modify BBC buttons. + IntegrationHook::call('integrate_bbc_buttons', [&self::$bbc_tags, &$editor_tag_map, &self::$disabled_tags]); + + // Generate a list of buttons that shouldn't be shown - this should be the fastest way to do this. + $disabled_bbc = !empty(Config::$modSettings['disabledBBC']) ? explode(',', Config::$modSettings['disabledBBC']) : []; + + if (empty(Config::$modSettings['disable_wysiwyg'])) { + self::$disabled_tags['removeformat'] = true; + self::$disabled_tags['orderedlist'] = true; + } + foreach ($disabled_bbc as $tag) { $tag = trim($tag); @@ -815,12 +898,21 @@ protected function buildSmileysToolbar(): void } /** - * Configures SCEditor options for this instance. + * Configures the options for the SCEditor instance and applies + * necessary plugins, styles, and other customizations. * - * Sets default dimensions, plugins, toolbar, emoticons, localization, and - * applies integration hooks and user-supplied overrides. + * This method sets default options for the SCEditor, including dimensions, + * style paths, plugins, toolbar configuration, emoticons, and localization + * settings. Additionally, it allows for customization via integration hooks + * and external editor options provided as arguments. * - * @param array $editorOptions SCEditor options/overrides. + * @param array $editorOptions An associative array of editor options provided externally. + * + * Behavior: + * 1. Initializes default plugins, enabling `autolinker` if URL auto-linking is enabled. + * 2. Configures SCEditor options such as dimensions, toolbar, colors, fonts, and parsing behavior. + * 3. Sets emoticons and their display behavior based on smiley toolbar configurations. + * 4. Provides integration hooks to allow further modification by mods. */ protected function setSCEditorOptions(array $editorOptions) { @@ -828,17 +920,22 @@ protected function setSCEditorOptions(array $editorOptions) $editorOptions['plugins'] = []; } - if (!empty(Config::$modSettings['autoLinkUrls']) && empty($editorOptions['disable_url_autolinking'])) { + if ($this->preview_type == self::PREVIEW_XML) { + $editorOptions['plugins'][] = 'xmlPreview'; + Theme::loadJavaScriptFile('sceditor.plugins.xml-preview.js', ['minimize' => true], 'smf_xml_preview'); + } + + if (!empty(Config::$modSettings['autoLinkUrls']) && empty($editorOptions['disable_url_autolinking']) && User::$me->allowedTo('bbc_url')) { $editorOptions['plugins'][] = 'autolinker'; Autolinker::createJavaScriptFile(); - Theme::loadJavaScriptFile('autolinker.js', ['minimize' => true], 'smf_autolinker'); - Theme::loadJavaScriptFile('sceditor.plugins.autolinker.js', ['minimize' => true], 'smf_autolinker_plugin'); + Theme::loadJavaScriptFile('sceditor.plugins.autolinker.js', ['minimize' => true], 'smf_autolinker'); } $this->sce_options = [ 'width' => $this->width ?? '100%', 'height' => $this->height ?? '250px', 'style' => Theme::$current->settings[file_exists(Theme::$current->settings['theme_dir'] . '/css/jquery.sceditor.default.css') ? 'theme_url' : 'default_theme_url'] . '/css/jquery.sceditor.default.css' . Utils::$context['browser_cache'], + 'autoUpdate' => true, 'emoticonsCompat' => true, 'emoticons' => [], 'emoticonsEnabled' => !$this->disable_smiley_box, @@ -918,7 +1015,7 @@ protected function setSCEditorOptions(array $editorOptions) *************************/ /** - * Initializes default BBC tags for the toolbar. + * Initializes BBC tags for the toolbar. */ protected static function initBbcTags(): void { @@ -927,7 +1024,7 @@ protected static function initBbcTags(): void /* array( 'code' => 'b', // Required - 'description' => Lang::$editortxt['bold'], // Required + 'description' => Lang::getTxt('bold', var: 'editortxt'), // Required 'image' => 'bold', // Optional 'before' => '[b]', // Optional 'after' => '[/b]', // Optional @@ -964,11 +1061,6 @@ protected static function initBbcTags(): void 'code' => 'tt', 'description' => Lang::getTxt('tt', var: 'editortxt'), ], - [ - 'image' => 'hidden', - 'code' => 'spoiler', - 'description' => Lang::getTxt('spoiler', var: 'editortxt'), - ], [], [ 'code' => 'pre', @@ -1052,17 +1144,12 @@ protected static function initBbcTags(): void 'code' => 'quote', 'description' => Lang::getTxt('insert_quote', var: 'editortxt'), ], - [ - 'image' => 'details', - 'code' => 'details', - 'description' => Lang::getTxt('details', var: 'editortxt'), - ], - [], [ 'image' => 'heading', 'code' => 'heading', 'description' => Lang::getTxt('heading', var: 'editortxt'), ], + [], [ 'code' => 'bulletlist', 'description' => Lang::getTxt('bullet_list', var: 'editortxt'), diff --git a/Sources/PersonalMessage/PM.php b/Sources/PersonalMessage/PM.php index eb9166b7670..204934e0276 100644 --- a/Sources/PersonalMessage/PM.php +++ b/Sources/PersonalMessage/PM.php @@ -595,7 +595,7 @@ public static function compose(): void Theme::loadJavaScriptFile('suggest.js', ['defer' => false, 'minimize' => true], 'smf_suggest'); if (Utils::$context['drafts_autosave']) { - Theme::loadJavaScriptFile('drafts.js', ['defer' => false, 'minimize' => true], 'smf_drafts'); + Theme::loadJavaScriptFile('sceditor.plugins.drafts.js', ['defer' => true, 'minimize' => true], 'smf_drafts'); } Utils::$context['sub_template'] = 'send'; @@ -792,7 +792,7 @@ public static function compose(): void } // Now create the editor. - new Editor([ + $editorOptions = [ 'id' => 'message', 'value' => Utils::$context['message'], 'height' => '175px', @@ -800,9 +800,41 @@ public static function compose(): void 'labels' => [ 'post_button' => Lang::getTxt('send_message', file: 'General'), ], + // We do XML preview here. 'preview_type' => Editor::PREVIEW_XML, + // This is a required field. 'required' => true, - ]); + // SCEditor plugins. + 'plugins' => [], + // SCEditor options. + 'options' => [ + 'previewOptions' => [ + 'sPreviewSectionContainerID' => 'preview_section', + 'sPreviewSubjectContainerID' => 'preview_subject', + 'sPreviewBodyContainerID' => 'preview_body', + 'sErrorsContainerID' => 'errors', + 'sErrorsSeriousContainerID' => 'error_serious', + 'sErrorsListContainerID' => 'error_list', + 'sCaptionContainerID' => 'caption_%ID%', + 'sTxtPreviewTitle' => Utils::escapeJavaScript(Lang::getTxt('preview_title', file: 'General')), + 'sTxtPreviewFetch' => Utils::escapeJavaScript(Lang::getTxt('preview_fetch', file: 'General')), + 'sUrl' => Config::$scripturl . '?action=pm;sa=send2;preview;xml', + ], + ], + ]; + + if (Utils::$context['drafts_autosave']) { + $editorOptions['plugins'][] = 'drafts'; + $editorOptions['plugins'][] = 'pmDrafts'; + $editorOptions['options']['draftOptions'] = [ + 'sLastNote' => 'draft_lastautosave', + 'sLastID' => 'id_draft', + 'sQueryParams' => 'action=pm;sa=send2;xml', + 'iFreq' => empty(Config::$modSettings['drafts_autosave_frequency']) ? 60000 : Config::$modSettings['drafts_autosave_frequency'] * 1000, + ]; + } + + new Editor($editorOptions); Utils::$context['bcc_value'] = ''; @@ -2163,8 +2195,7 @@ public static function reportErrors(array $error_types, array $named_recipients, } } - // Create it... - new Editor([ + $editorOptions = [ 'id' => 'message', 'value' => Utils::$context['message'], 'width' => '90%', @@ -2172,8 +2203,41 @@ public static function reportErrors(array $error_types, array $named_recipients, 'labels' => [ 'post_button' => Lang::getTxt('send_message', file: 'General'), ], + // We do XML preview here. 'preview_type' => Editor::PREVIEW_XML, - ]); + // This is a required field. + 'required' => true, + // SCEditor plugins. + 'plugins' => [], + // SCEditor options. + 'options' => [ + 'previewOptions' => [ + 'sPreviewSectionContainerID' => 'preview_section', + 'sPreviewSubjectContainerID' => 'preview_subject', + 'sPreviewBodyContainerID' => 'preview_body', + 'sErrorsContainerID' => 'errors', + 'sErrorsSeriousContainerID' => 'error_serious', + 'sErrorsListContainerID' => 'error_list', + 'sCaptionContainerID' => 'caption_%ID%', + 'sTxtPreviewTitle' => Utils::escapeJavaScript(Lang::getTxt('preview_title', file: 'General')), + 'sTxtPreviewFetch' => Utils::escapeJavaScript(Lang::getTxt('preview_fetch', file: 'General')), + 'sUrl' => Config::$scripturl . '?action=pm;sa=send2;preview;xml', + ], + ], + ]; + + if (Utils::$context['drafts_autosave']) { + $editorOptions['plugins'][] = 'drafts'; + $editorOptions['plugins'][] = 'pmDrafts'; + $editorOptions['options']['draftOptions'] = [ + 'sLastNote' => 'draft_lastautosave', + 'sLastID' => 'id_draft', + 'sQueryParams' => 'action=pm;sa=send2;xml', + 'iFreq' => empty(Config::$modSettings['drafts_autosave_frequency']) ? 60000 : Config::$modSettings['drafts_autosave_frequency'] * 1000, + ]; + } + + new Editor($editorOptions); // Check whether we need to show the code again. Utils::$context['require_verification'] = !User::$me->is_admin && !empty(Config::$modSettings['pm_posts_verification']) && User::$me->posts < Config::$modSettings['pm_posts_verification']; diff --git a/Sources/Profile.php b/Sources/Profile.php index 45cc848530d..0aef2625853 100644 --- a/Sources/Profile.php +++ b/Sources/Profile.php @@ -304,7 +304,7 @@ public function loadStandardFields(bool $force_reload = false) * - text: A string of some description. * * string $label: The label for this item. Default will be - * Lang::$txt[$key] if this isn't set. + * Lang::getTxt($key) if this isn't set. * * string $subtext: The subtext (Small label) for this item. * @@ -1269,12 +1269,7 @@ public function loadAvatarData(): bool } // Get a list of all the server stored avatars. - if ($this->formatted['avatar']['allow_server_stored']) { - Utils::$context['avatar_list'] = []; - Utils::$context['avatars'] = is_dir(Config::$modSettings['avatar_directory']) ? $this->getAvatars('', 0) : []; - } else { - Utils::$context['avatars'] = []; - } + Utils::$context['avatars'] = $this->formatted['avatar']['allow_server_stored'] && is_dir(Config::$modSettings['avatar_directory']) ? $this->getAvatars('', 0) : []; // Second level selected avatar... Utils::$context['avatar_selected'] = substr((string) strrchr($this->formatted['avatar']['server_pic'], '/'), 1); @@ -1488,20 +1483,13 @@ public function setupContext(array $fields): void } } - // Some spicy JS. + Theme::addJavaScriptVar('require_password', !empty(Utils::$context['require_password']), true); + Theme::addJavaScriptVar('required_security_reasons', Lang::getTxt('required_security_reasons', file: 'Profile'), true); + Theme::addJavaScriptVar('autodetect', Lang::getTxt('timeoffset_autodetect', file: 'Profile'), true); + + // Backwards compatibility. Theme::addInlineJavaScript(' - var form_handle = document.forms.creator; - createEventListener(form_handle); - ' . (!empty(Utils::$context['require_password']) ? ' - form_handle.addEventListener("submit", function(event) - { - if (this.oldpasswrd.value == "") - { - event.preventDefault(); - alert(' . (Utils::escapeJavaScript(Lang::getTxt('required_security_reasons', file: 'Profile'))) . '); - return false; - } - }, false);' : ''), true); + var form_handle = document.forms.creator;', true); // Any onsubmit JavaScript? if (!empty(Utils::$context['profile_onsubmit_javascript'])) { @@ -2540,7 +2528,7 @@ protected function prepareToSaveOptions(): void * @param int $level How many levels we should go in the directory. * @return array An array of information about the files and directories found. */ - protected function getAvatars(string $directory, int $level = 0): array + protected function getAvatars(string $directory = '', int $level = 0): array { $result = []; @@ -2548,7 +2536,7 @@ protected function getAvatars(string $directory, int $level = 0): array $files = []; // Open the directory.. - $dir = dir(Config::$modSettings['avatar_directory'] . (!empty($directory) ? '/' : '') . $directory); + $dir = dir(Config::$modSettings['avatar_directory'] . '/' . $directory); if (!$dir) { return []; @@ -2580,10 +2568,12 @@ protected function getAvatars(string $directory, int $level = 0): array ]; } + $directory .= $directory != '' ? '/' : ''; + foreach ($dirs as $line) { - $tmp = $this->getAvatars($directory . (!empty($directory) ? '/' : '') . $line, $level + 1); + $tmp = $this->getAvatars($directory . $line, $level + 1); - if (!empty($tmp)) { + if ($tmp != []) { $result[] = [ 'filename' => Utils::htmlspecialchars($line), 'checked' => str_contains(Utils::$context['member']['avatar']['server_pic'], $line . '/'), @@ -2597,32 +2587,20 @@ protected function getAvatars(string $directory, int $level = 0): array } foreach ($files as $line) { - $filename = substr($line, 0, (\strlen($line) - \strlen(strrchr($line, '.')))); - $extension = substr(strrchr($line, '.'), 1); + $found = preg_match('/([^.]+)\.(jpe?g|png|gif|bmp)$/i', $line, $match); // Make sure it is an image. // @todo Change this to use MIME type. - if ( - strcasecmp($extension, 'gif') != 0 - && strcasecmp($extension, 'jpg') != 0 - && strcasecmp($extension, 'jpeg') != 0 - && strcasecmp($extension, 'png') != 0 - && strcasecmp($extension, 'bmp') != 0 - && strcasecmp($extension, 'webp') != 0 - ) { + if ($found !== 1) { continue; } $result[] = [ 'filename' => Utils::htmlspecialchars($line), - 'checked' => $line == Utils::$context['member']['avatar']['server_pic'], - 'name' => Utils::htmlspecialchars(str_replace('_', ' ', $filename)), + 'checked' => $directory . $line == Utils::$context['member']['avatar']['server_pic'], + 'name' => Utils::htmlspecialchars(str_replace('_', ' ', $match[1])), 'is_dir' => false, ]; - - if ($level == 1) { - Utils::$context['avatar_list'][] = $directory . '/' . $line; - } } return $result; @@ -2666,8 +2644,6 @@ protected function setAvatarServerStored(string $filename): void if ( // Named 'blank.png' $this->new_data['avatar'] == 'blank.png' - // Not inside the expected directory. - || !str_starts_with($avatar_path, Config::$modSettings['avatar_directory'] . '/') // Not a file. || !is_file($avatar_path) // Not a valid image file. diff --git a/Sources/Theme.php b/Sources/Theme.php index 2edcc50bbc5..3c0326e0383 100644 --- a/Sources/Theme.php +++ b/Sources/Theme.php @@ -2269,6 +2269,10 @@ protected function loadVariant(): void if (Utils::$context['theme_variant'] == '' || !\in_array(Utils::$context['theme_variant'], $this->settings['theme_variants'])) { Utils::$context['theme_variant'] = !empty($this->settings['default_variant']) && \in_array($this->settings['default_variant'], $this->settings['theme_variants']) ? $this->settings['default_variant'] : $this->settings['theme_variants'][0]; } + + if (!empty(Utils::$context['theme_variant']) && Utils::$context['theme_variant'] !== 'default') { + self::loadCSSFile('index_' . Utils::$context['theme_variant'] . '.css', ['order_pos' => 2], 'smf_index' . Utils::$context['theme_variant']); + } } } @@ -2280,6 +2284,7 @@ protected function loadJavaScript(): void // Default JS variables for use in every theme Utils::$context['javascript_vars'] = [ 'smf_theme_url' => '"' . $this->settings['theme_url'] . '"', + 'smf_theme_id' => self::$current->settings['theme_id'], 'smf_default_theme_url' => '"' . $this->settings['default_theme_url'] . '"', 'smf_images_url' => '"' . $this->settings['images_url'] . '"', 'smf_smileys_url' => '"' . Config::$modSettings['smileys_url'] . '"', @@ -2323,16 +2328,11 @@ protected function loadJavaScript(): void } // Queue our JQuery plugins! - self::loadJavaScriptFile('smf_jquery_plugins.js', ['minimize' => true], 'smf_jquery_plugins'); - - if (!User::$me->is_guest) { - self::loadJavaScriptFile('jquery.custom-scrollbar.js', ['minimize' => true], 'smf_jquery_scrollbar'); - self::loadCSSFile('jquery.custom-scrollbar.css', ['force_current' => false, 'validate' => true], 'smf_scrollbar'); - } + self::loadJavaScriptFile('smf_jquery_plugins.js', ['defer' => true, 'minimize' => true], 'smf_jquery_plugins'); // script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all. self::loadJavaScriptFile('script.js', ['defer' => false, 'minimize' => true], 'smf_script'); - self::loadJavaScriptFile('theme.js', ['minimize' => true], 'smf_theme'); + self::loadJavaScriptFile('theme.js', ['defer' => true, 'minimize' => true], 'smf_theme'); // And we should probably trigger the cron too. if (empty(Config::$modSettings['cron_is_real_cron'])) { diff --git a/Themes/default/Admin.template.php b/Themes/default/Admin.template.php index 2be9b2150e3..50ade262c9a 100644 --- a/Themes/default/Admin.template.php +++ b/Themes/default/Admin.template.php @@ -75,12 +75,13 @@ function template_admin() - '; + +
'; foreach (Utils::$context[Utils::$context['admin_menu_name']]['sections'] as $area_id => $area) { echo ' -
- ', $area['title'], ''; + ', $area['title'], ' +
'; foreach ($area['areas'] as $item_id => $item) { // No point showing the 'home' page here, we're already on it! @@ -90,19 +91,22 @@ function template_admin() $url = $item['url'] ?? Config::$scripturl . '?action=admin;area=' . $item_id . (!empty(Utils::$context[Utils::$context['admin_menu_name']]['extra_parameters']) ? Utils::$context[Utils::$context['admin_menu_name']]['extra_parameters'] : ''); - if (!empty($item['icon_file'])) { - echo ' - ', $item['label'], ''; - } else { echo ' - ', $item['label'], ''; - } + + + ', !empty($item['icon_file']) ? '' : '', ' + + ', $item['label'], ' + '; } echo ' -
'; +
'; } + echo ' + '; + // The below functions include all the scripts needed from the simplemachines.org site. The language and format are passed for internationalization. if (empty(Config::$modSettings['disable_smf_js'])) { echo ' @@ -731,7 +735,7 @@ function template_not_done()
'; // Do we have a token? - if (isset(Utils::$context['not_done_token'], Utils::$context[Utils::$context['not_done_token'] . '_token'],Utils::$context[Utils::$context['not_done_token'] . '_token_var'])) { + if (isset(Utils::$context['not_done_token'], Utils::$context[Utils::$context['not_done_token'] . '_token'], Utils::$context[Utils::$context['not_done_token'] . '_token_var'])) { echo ' '; } @@ -768,9 +772,6 @@ function template_show_settings() echo Utils::$context['settings_insert_above']; } - echo ' - '; - // Is there a custom title? if (isset(Utils::$context['settings_title'])) { echo ' @@ -823,49 +824,33 @@ function ($v) { }, ); - // Now actually loop through all the variables. - $is_open = false; + echo ' + '; foreach (Utils::$context['config_vars'] as $config_var) { // Is it a title or a description? if (is_array($config_var) && ($config_var['type'] == 'title' || $config_var['type'] == 'desc')) { - // Not a list yet? - if ($is_open) { - $is_open = false; - echo ' - - '; - } - // A title? if ($config_var['type'] == 'title') { echo ' -
-

+
+

', ($config_var['help'] ? '' : ''), ' ', $config_var['label'], ' -

+

'; } // A description? else { echo ' -
+

', $config_var['label'], ' -

'; +

'; } continue; } - // Not a list yet? - if (!$is_open) { - $is_open = true; - echo ' -
-
'; - } - // Hang about? Are you pulling my leg - a callback?! if (is_array($config_var) && $config_var['type'] == 'callback') { if (function_exists('template_callback_' . $config_var['name'])) { @@ -879,14 +864,14 @@ function ($v) { // First off, is this a span like a message? if (in_array($config_var['type'], ['message', 'warning'])) { echo ' - + ', $config_var['label'], ' - '; +
'; } // Otherwise it's an input box of some kind. else { echo ' - '; + '; // Some quick helpers... $javascript = $config_var['javascript']; @@ -905,8 +890,8 @@ function ($v) { echo ' ', $config_var['label'], '', $subtext, ($config_var['type'] == 'password' ? '
' . Lang::getTxt('admin_confirm_password', file: 'Admin') . '' : ''), ' - - ', + + ', $config_var['preinput']; // Show a check box. @@ -1033,40 +1018,27 @@ function ($v) { echo isset($config_var['postinput']) ? ' ' . $config_var['postinput'] : '', ' - '; + '; } } else { // Just show a separator. if ($config_var == '') { echo ' - -
-
'; +
'; } else { echo ' -
+

' . $config_var . ' -

-
'; +

'; } } } - if ($is_open) { - echo ' -
'; - } - if (empty(Utils::$context['settings_save_dont_show'])) { echo ' '; } - if ($is_open) { - echo ' - '; - } - // At least one token has to be used! if (isset(Utils::$context['admin-ssc_token'])) { echo ' @@ -1384,12 +1356,12 @@ function template_admin_search_results() { echo '
- ', template_admin_quick_search(), '

', Lang::getTxt('admin_search_results_desc', Utils::$context, file: 'Admin'), '

+ ', template_admin_quick_search(), '
'; diff --git a/Themes/default/BoardIndex.template.php b/Themes/default/BoardIndex.template.php index ee42a660880..622f0a0cc5c 100644 --- a/Themes/default/BoardIndex.template.php +++ b/Themes/default/BoardIndex.template.php @@ -18,14 +18,6 @@ use SMF\User; use SMF\Utils; -/** - * The top part of the outer layer of the boardindex - */ -function template_boardindex_outer_above() -{ - template_newsfader(); -} - /** * This shows the newsfader */ @@ -34,15 +26,18 @@ function template_newsfader() // Show the news fader? (assuming there are things to show...) if (!empty(Theme::$current->settings['show_newsfader']) && !empty(Utils::$context['news_lines'])) { echo ' -
    '; +
    +
    +
      '; foreach (Utils::$context['news_lines'] as $news) { echo ' -
    • ', $news, '
    • '; +
    • ', $news, '
    • '; } echo ' -
    +
+
'; + + + '; } /** @@ -1024,94 +953,32 @@ function in_array(variable, theArray) */ function template_hms() { - $alt = false; - echo ' - - - - '; +
+

Binary Clock

+
+
+
    '; foreach (Utils::$context['clockicons'] as $t => $v) { echo ' -
- - '; - - $alt = !$alt; - } - echo ' - - - -
Binary Clock
'; +
    '; foreach ($v as $i) { echo ' - '; +
  • '; } echo ' -
- Too tough for you? -
'; - - echo ' - '; + + + '; } /** @@ -1119,92 +986,29 @@ function in_array(variable, theArray) */ function template_omfg() { - $alt = false; - echo ' - - - - '; +
+

OMFG Binary Clock

+
+
+
    '; foreach (Utils::$context['clockicons'] as $t => $v) { echo ' -
- - '; - - $alt = !$alt; + '; } echo ' -
OMFG Binary Clock
'; +
    '; foreach ($v as $i) { echo ' - '; +
  • '; } echo ' -
- '; + + '; } /** @@ -1212,31 +1016,27 @@ function in_array(variable, theArray) */ function template_thetime() { - $alt = false; - echo ' - - - - '; +
+

OMFG Binary Clock

+
+
+
    '; - foreach (Utils::$context['clockicons'] as $v) { + foreach (Utils::$context['clockicons'] as $t => $v) { echo ' -
- - '; - - $alt = !$alt; + '; } echo ' -
The time you requested
'; +
    '; foreach ($v as $i) { echo ' - '; + '; } echo ' -
'; + + '; } diff --git a/Themes/default/Display.template.php b/Themes/default/Display.template.php index a1ce5d13148..e963c8be7fc 100644 --- a/Themes/default/Display.template.php +++ b/Themes/default/Display.template.php @@ -41,7 +41,7 @@ function template_main() // Show new topic info here? echo ' -
+

', Utils::$context['subject'], '', (Utils::$context['is_locked']) ? ' ' : '', (Utils::$context['is_sticky']) ? ' ' : '', '

@@ -49,7 +49,7 @@ function template_main() // Next - Prev echo ' - ', Utils::$context['previous_next'], ''; + ', Utils::$context['previous_next'], ''; if (!empty(Theme::$current->settings['display_who_viewing'])) { // Show just numbers...? @@ -191,12 +191,12 @@ function template_main() // Show the page index... "Pages: [1]". echo '
- ', template_button_strip(Utils::$context['normal_buttons'], 'right'), ' - ', Utils::$context['menu_separator'], ' '; +
+ ', Utils::$context['menu_separator'], ' + ', template_button_strip(Utils::$context['normal_buttons'], 'right'), ''; // Mobile action - moderation buttons (top) if (!empty(Utils::$context['normal_buttons'])) { @@ -212,8 +212,8 @@ function template_main() // Show the topic information - icon, subject, etc. echo ' -
- '; + +
'; Utils::$context['ignoredMsgs'] = []; Utils::$context['removableMessageIDs'] = []; @@ -224,18 +224,18 @@ function template_main() } echo ' - -
'; +
+ '; // Show the page index... "Pages: [1]". echo '
- ', template_button_strip(Utils::$context['normal_buttons'], 'right'), ' - ', Utils::$context['menu_separator'], ' '; +
+ ', Utils::$context['menu_separator'], ' + ', template_button_strip(Utils::$context['normal_buttons'], 'right'), ''; // Mobile action - moderation buttons (bottom) if (!empty(Utils::$context['normal_buttons'])) { @@ -301,40 +301,57 @@ function template_main() if (!empty(Theme::$current->options['display_quick_mod']) && Theme::$current->options['display_quick_mod'] == 1 && Utils::$context['can_remove_post']) { echo ' - const strips = [ - { id: "moderationbuttons", display: "moderationbuttons_strip", varName: "oInTopicModeration" }, - { id: "moderationbuttons_mobile", display: "moderationbuttons_strip_mobile", varName: "oInTopicModerationMobile" } - ]; - - for (let i = 0; i < strips.length; i++) { - const strip = strips[i]; - window[strip.varName] = new InTopicModeration({ - sCheckboxContainerMask: "in_topic_mod_check_", - aMessageIds: ["', implode('", "', Utils::$context['removableMessageIDs']), '"], - sSessionId: smf_session_id, - sSessionVar: smf_session_var, - sButtonStrip: strip.id, - sButtonStripDisplay: strip.display, - bUseImageButton: false, - bCanRemove: ', Utils::$context['can_remove_post'] ? 'true' : 'false', ', - sRemoveButtonLabel: "', Lang::getTxt('quickmod_delete_selected', file: 'General'), '", - sRemoveButtonImage: "delete_selected.png", - sRemoveButtonConfirm: "', Lang::getTxt('quickmod_confirm', file: 'General'), '", - bCanRestore: ', Utils::$context['can_restore_msg'] ? 'true' : 'false', ', - sRestoreButtonLabel: "', Lang::getTxt('quick_mod_restore', file: 'General'), '", - sRestoreButtonImage: "restore_selected.png", - sRestoreButtonConfirm: "', Lang::getTxt('quickmod_confirm', file: 'General'), '", - bCanSplit: ', Utils::$context['can_split'] ? 'true' : 'false', ', - sSplitButtonLabel: "', Lang::getTxt('quickmod_split_selected', file: 'General'), '", - sSplitButtonImage: "split_selected.png", - sSplitButtonConfirm: "', Lang::getTxt('quickmod_confirm', file: 'General'), '", - sFormId: "quickModForm" - }); - }'; + var oInTopicModeration = new InTopicModeration({ + sCheckboxContainerMask: \'in_topic_mod_check_\', + aMessageIds: [\'', implode('\', \'', Utils::$context['removableMessageIDs']), '\'], + sSessionId: smf_session_id, + sSessionVar: smf_session_var, + sButtonStrip: \'moderationbuttons\', + sButtonStripDisplay: \'moderationbuttons_strip\', + bUseImageButton: false, + bCanRemove: ', Utils::$context['can_remove_post'] ? 'true' : 'false', ', + sRemoveButtonLabel: \'', Lang::getTxt('quickmod_delete_selected', file: 'General'), '\', + sRemoveButtonImage: \'delete_selected.png\', + sRemoveButtonConfirm: \'', Lang::getTxt('quickmod_confirm', file: 'General'), '\', + bCanRestore: ', Utils::$context['can_restore_msg'] ? 'true' : 'false', ', + sRestoreButtonLabel: \'', Lang::getTxt('quick_mod_restore', file: 'General'), '\', + sRestoreButtonImage: \'restore_selected.png\', + sRestoreButtonConfirm: \'', Lang::getTxt('quickmod_confirm', file: 'General'), '\', + bCanSplit: ', Utils::$context['can_split'] ? 'true' : 'false', ', + sSplitButtonLabel: \'', Lang::getTxt('quickmod_split_selected', file: 'General'), '\', + sSplitButtonImage: \'split_selected.png\', + sSplitButtonConfirm: \'', Lang::getTxt('quickmod_confirm', file: 'General'), '\', + sFormId: \'quickModForm\' + });'; + + // Add it to the mobile button strip as well + echo ' + var oInTopicModerationMobile = new InTopicModeration({ + sCheckboxContainerMask: \'in_topic_mod_check_\', + aMessageIds: [\'', implode('\', \'', Utils::$context['removableMessageIDs']), '\'], + sSessionId: smf_session_id, + sSessionVar: smf_session_var, + sButtonStrip: \'moderationbuttons_mobile\', + sButtonStripDisplay: \'moderationbuttons_strip_mobile\', + bUseImageButton: false, + bCanRemove: ', Utils::$context['can_remove_post'] ? 'true' : 'false', ', + sRemoveButtonLabel: \'', Lang::getTxt('quickmod_delete_selected', file: 'General'), '\', + sRemoveButtonImage: \'delete_selected.png\', + sRemoveButtonConfirm: \'', Lang::getTxt('quickmod_confirm', file: 'General'), '\', + bCanRestore: ', Utils::$context['can_restore_msg'] ? 'true' : 'false', ', + sRestoreButtonLabel: \'', Lang::getTxt('quick_mod_restore', file: 'General'), '\', + sRestoreButtonImage: \'restore_selected.png\', + sRestoreButtonConfirm: \'', Lang::getTxt('quickmod_confirm', file: 'General'), '\', + bCanSplit: ', Utils::$context['can_split'] ? 'true' : 'false', ', + sSplitButtonLabel: \'', Lang::getTxt('quickmod_split_selected', file: 'General'), '\', + sSplitButtonImage: \'split_selected.png\', + sSplitButtonConfirm: \'', Lang::getTxt('quickmod_confirm', file: 'General'), '\', + sFormId: \'quickModForm\' + });'; } echo ' - new QuickModify({ + var oQuickModify = new QuickModify({ sScriptUrl: smf_scripturl, sFormName: \'quickModForm\', sClassName: \'quick_edit\', @@ -342,6 +359,7 @@ function template_main() iTopicId: ', Utils::$context['current_topic'], ', sSaveButtonText: ', Utils::escapeJavaScript(Lang::getTxt('save', file: 'General')), ', sCancelButtonText: ', Utils::escapeJavaScript(Lang::getTxt('modify_cancel', file: 'General')), ', + sTemplateReasonEdit: ', Utils::escapeJavaScript(Lang::getTxt('reason_for_edit', file: 'General')) . ', sEditReasonText: ', Utils::escapeJavaScript(Lang::getTxt('reason_for_edit', file: 'General')) . ', sErrorBorderStyle: ', Utils::escapeJavaScript('1px solid red'), ' }); @@ -367,6 +385,17 @@ function template_main() iTopicId: ', Utils::$context['current_topic'], ', sSessionId: smf_session_id, sSessionVar: smf_session_var, + sLabelIconList: "', Lang::getTxt('message_icon', file: 'General'), '", + sBoxBackground: "transparent", + sBoxBackgroundHover: "#ffffff", + iBoxBorderWidthHover: 1, + sBoxBorderColorHover: "#adadad" , + sContainerBackground: "#ffffff", + sContainerBorder: "1px solid #adadad", + sItemBorder: "1px solid #ffffff", + sItemBorderHover: "1px dotted gray", + sItemBackground: "transparent", + sItemBackgroundHover: "#e0e0f0" sLabelIconList: "', Lang::getTxt('message_icon', file: 'General'), '" });'; @@ -388,6 +417,8 @@ function template_main() function template_single_post($message) { $ignoring = false; + $show_subject = !empty(Config::$modSettings['subject_toggle']); + $is_first_post = $message['id'] == Utils::$context['first_message']; if ($message['can_remove']) { Utils::$context['removableMessageIDs'][] = $message['id']; @@ -413,31 +444,77 @@ function template_single_post($message) // Show the message. echo ' -
-
'; +
+
+ ', !$is_first_post ? ' + ' . ($message['first_new'] ? '' : '') : ''; + + echo ' +
'; + + echo ' + <', $show_subject ? 'p' : 'h3', ' class="page_number" id="msg_num_', $message['id'], '">', $is_first_post ? Lang::getTxt('first_post', file: 'General') : (!empty($message['counter']) ? Lang::getTxt('reply_number_sr', [$message['counter']]) : ''), ''; + + // Some people don't want subject... The div is still required or quick edit breaks. + echo ' +
+ <', $show_subject ? 'h3' : 'p', '>', $message['subject'], ' +
+
'; + + echo ' + '; + + // Show "<< Last Edit: Time by Person >>" if this post was edited. But we need the div even if it wasn't modified! + // Because we insert into it through AJAX and we don't want to stop themers moving it around if they so wish so they can put it where they want it. + echo ' +

'; + + if (!empty(Config::$modSettings['show_modify']) && !empty($message['modified']['name'])) { + echo + $message['modified']['last_edit_text']; + } + + echo ' +

+ '; + + if (!$message['approved'] && $message['member']['id'] != 0 && $message['member']['id'] == User::$me->id) { + echo ' +

+ ', Lang::getTxt('post_awaiting_approval', file: 'General'), ' +

'; + } + + echo ' +
'; // Show information about the poster of this message. echo ' -
'; +
'; // Are there any custom fields above the member name? if (!empty($message['custom_fields']['above_member'])) { echo ' -
-
    '; + '; foreach ($message['custom_fields']['above_member'] as $custom) { echo ' -
  • ', $custom['value'], '
  • '; +
  • ', $custom['value'], '
  • '; } echo ' -
-
'; + '; } echo ' -

'; + '; // Show online and offline buttons? if (!empty(Config::$modSettings['onlineEnable']) && !$message['member']['is_guest']) { @@ -467,34 +544,34 @@ function template_single_post($message) // Begin display of user info echo ' -

+
'; } // Otherwise, you see NOTHING! else { echo ' -
  • ', Lang::getTxt('logged', file: 'General'), '
  • '; +
    ', Lang::getTxt('logged', file: 'General'), '
    '; } // Are we showing the warning status? // Don't show these things for guests. if (!$message['member']['is_guest'] && $message['member']['can_see_warning']) { echo ' -
  • - ', Utils::$context['can_issue_warning'] ? '' : '', ' ', Utils::$context['can_issue_warning'] ? '' : '', '', Lang::getTxt('warn_' . $message['member']['warning_status'], file: 'General'), ' -
  • '; +
    + ', Utils::$context['can_issue_warning'] ? '' : '', ' ', Utils::$context['can_issue_warning'] ? '' : '', '', Lang::getTxt('warn_' . $message['member']['warning_status'], file: 'General'), ' +
    '; } // Are there any custom fields to show at the bottom of the poster info? if (!empty($message['custom_fields']['bottom_poster'])) { foreach ($message['custom_fields']['bottom_poster'] as $custom) { echo ' -
  • ', $custom['value'], '
  • '; +
    ', $custom['value'], '
    '; } } // Poster info ends. echo ' -
    -
    -
    '; - - // Some people don't want subject... The div is still required or quick edit breaks. - echo ' -
    - ', $message['link'], ' -
    '; - - echo ' - ', !empty($message['counter']) ? '#' . $message['counter'] . '' : '', ' - - -
    '; + +
    '; // Ignoring this user? Hide the post. if ($ignoring) { echo ' -
    +
    '; + + '; } // Show the post itself, finally! echo '
    '; - if (!$message['approved'] && $message['member']['id'] != 0 && $message['member']['id'] == User::$me->id) { - echo ' -
    - ', Lang::getTxt('post_awaiting_approval', file: 'General'), ' -
    '; - } echo ' +
    +
    '; // Are there any custom profile fields for above the signature? @@ -900,8 +905,8 @@ function template_single_post($message) echo '
    -
    -
    + +
    '; } @@ -977,7 +982,7 @@ function template_quickreply() '; } - template_control_richedit('quickReply', true, true); + template_control_richedit('quickReply', 'smileyBox_message', 'bbcBox_message'); // Is visual verification enabled? if (Utils::$context['require_verification']) { @@ -1000,46 +1005,10 @@ function template_quickreply()

    '; - // Draft autosave available and the user has it enabled? - if (!empty(Utils::$context['drafts_autosave'])) { - echo ' - '; - } - if (Utils::$context['show_spellchecking']) { echo '
    '; } - - echo ' - '; } diff --git a/Themes/default/Errors.template.php b/Themes/default/Errors.template.php index 7d682d741ae..3ddff5c9100 100644 --- a/Themes/default/Errors.template.php +++ b/Themes/default/Errors.template.php @@ -16,7 +16,6 @@ use SMF\Theme; use SMF\Utils; -// @todo /* This template file contains only the sub template fatal_error. It is shown when an error occurs, and should show at least a back button and Utils::$context['error_message']. @@ -27,12 +26,12 @@ */ function template_fatal_error() { - if (!empty(Utils::$context['simple_action'])) { + if (!empty(Utils::$context['simple_action']) || !empty(Utils::$context['from_ajax'])) { echo ' - +

    ', Utils::$context['error_title'], ' -
    -
    +

    +
    ', Utils::$context['error_message'], '
    '; } else { @@ -43,10 +42,8 @@ function template_fatal_error() ', Utils::$context['error_title'], '
    -
    -
    - ', Utils::$context['error_message'], ' -
    +
    + ', Utils::$context['error_message'], '
    '; @@ -112,28 +109,35 @@ function template_error_log() -
    - - -
    +
      +
    • + + +
    • +
    '; // We have some errors, must be some mods installed :P foreach (Utils::$context['errors'] as $error) { echo '
    -
    ', $error['id'], '
    - - -
    - ', $error['time'], ' + + + ', $error['time'], ' +
    -
    +
    + #', $error['id'], ' + +
    -
    -
    +
    +
    + ', Lang::getTxt('backtrace_title', file: 'ManageMaintenance'), ' +
    +
    ', $error['member']['link'], ''; @@ -163,19 +167,15 @@ function template_error_log() echo '
    -
    - - ', Lang::getTxt('backtrace_title', file: 'ManageMaintenance'), ' - + +
    +
    + ', Lang::getTxt('error_type_name', ['type' => $error['error_type']['type'] === 'critical' ? '' . $error['error_type']['name'] . '' : $error['error_type']['name']]), '
    + + ', $error['message']['html'], '
    -
    -
    - ', Lang::getTxt('error_type_name', ['type' => $error['error_type']['type'] === 'critical' ? '' . $error['error_type']['name'] . '' : $error['error_type']['name']], file: 'ManageMaintenance'), '
    - - ', $error['message']['html'], ' -
    '; } diff --git a/Themes/default/EventEditor.template.php b/Themes/default/EventEditor.template.php index 805511e16ae..fd141e07b28 100644 --- a/Themes/default/EventEditor.template.php +++ b/Themes/default/EventEditor.template.php @@ -226,7 +226,7 @@ function template_event_new()
    - +
    '; @@ -717,9 +717,9 @@ function template_linked_events() } } - if (!empty($event->location)) { + if ($event['location'] != '') { echo ' -
    ', $event->location; +
    ', nl2br($event['location']); } $rrule_description = $event->getParentEvent()->recurrence_iterator->getRRule()->getDescription($event); diff --git a/Themes/default/GenericControls.template.php b/Themes/default/GenericControls.template.php index 402b290e5f1..8a923b0af23 100644 --- a/Themes/default/GenericControls.template.php +++ b/Themes/default/GenericControls.template.php @@ -39,12 +39,10 @@ function template_control_richedit(string $editor_id, bool|string|null $smiley_c $editor_context = Editor::$loaded[$editor_id]; echo ' - -
    - + '; @@ -57,6 +55,7 @@ function template_control_richedit(string $editor_id, bool|string|null $smiley_c * functionality like auto-saving drafts if enabled. * * @param string $editor_id The unique ID of the editor for which buttons are displayed. + * */ function template_control_richedit_buttons(string $editor_id): void { @@ -81,7 +80,7 @@ function template_control_richedit_buttons(string $editor_id): void } echo ' - + '; // Include auto-save feature if drafts are enabled. @@ -90,18 +89,7 @@ function template_control_richedit_buttons(string $editor_id): void - - '; + '; } } @@ -219,3 +207,92 @@ function template_control_verification(int|string $verify_id, string $display_ty return true; } } + +/** + * Renders a UI for choosing boards within categories. + * + * This function outputs a set of fieldsets representing categories, + * each containing a nested list of boards. Boards can be selected using checkboxes. + * The function also handles the display of child boards in a hierarchical structure + * and adds JavaScript functionality to enable selecting or deselecting all boards within a category. + * + * @param array $categories An array of categories, each containing: + * - 'name': The name of the category. + * - 'boards': An array of boards with: + * - 'id': The unique identifier for the board. + * - 'name': The display name of the board. + * - 'selected': Whether the board is selected (boolean). + * - 'child_level': The hierarchical level of the board (integer). + */ +function template_choose_boards(array $categories): void +{ + foreach ($categories as $category) { + echo ' +
    + + ', $category['name'], ' + +
      '; + + for ($i = 0, $n = count($category['boards']); $i < $n; $i++) { + echo ' +
    • + '; + + // Nest child boards inside another list. + $curr_child_level = $category['boards'][$i]['child_level']; + $next_child_level = $category['boards'][$i + 1]['child_level'] ?? 0; + + if ($next_child_level > $curr_child_level) { + echo ' +
        '; + } else { + // Close child board lists until we reach a common level + // with the next board. + while ($next_child_level < $curr_child_level--) { + echo ' + +
      '; + } + + echo '
    • '; + } + } + + echo ' +
    +
    '; + } + + echo ' + '; +} diff --git a/Themes/default/GenericMenu.template.php b/Themes/default/GenericMenu.template.php index 7ebb6534930..bcc8549ac1d 100644 --- a/Themes/default/GenericMenu.template.php +++ b/Themes/default/GenericMenu.template.php @@ -29,21 +29,21 @@ function template_generic_menu_dropdown_above() // Load the menu // Add mobile menu as well echo ' - - - ', Lang::getTxt('mobile_generic_menu', ['label' => $menu_label], file: 'General'), ' - -
    +
    + - '; + '; if (!empty(Utils::$context['can_register'])) { echo '
    -
    +

    ', Lang::getTxt('register_prompt', ['scripturl' => Config::$scripturl], file: 'General'), ' -

    '; - } - - // It is a long story as to why we have this when we're clearly not going to use it. - if (!empty(Utils::$context['from_ajax'])) { - echo ' -
    - '; +

    '; } echo ' -
    + '; + + if (empty(Utils::$context['from_ajax'])) { + echo '
    '; + } } /** @@ -182,14 +133,18 @@ function template_login() */ function template_login_tfa() { - echo ' + if (empty(Utils::$context['from_ajax'])) { + echo ' '; + } } /** @@ -294,16 +220,17 @@ function template_kick_guest() { // This isn't that much... just like normal login but with a message at the top. echo ' -
    - +

    +
    +

    + ', Lang::getTxt('login', file: 'General'), ' +

    +
    +
    + +
    + +
    + + + +

    + ', Lang::getTxt('forgot_your_password', file: 'General'), ' +

    +
    + +
    '; - - // Do the focus thing... - echo ' - '; } /** @@ -358,38 +273,31 @@ function template_maintenance() { // Display the administrator's message at the top. echo ' -
    - + +
    +

    ', Utils::$context['title'], '

    +
    +

    + ', Lang::getTxt('in_maintain_mode', file: 'Login'), ' + ', Utils::$context['description'], '
    +

    +
    +

    ', Lang::getTxt('admin_login', file: 'General'), '

    +
    +
    + +
    + +
    + + + +
    + +
    '; } @@ -400,14 +308,14 @@ function template_admin_login() { // Since this should redirect to whatever they were doing, send all the get data. echo ' -
    + -
    '; - - // Focus on the password box. - echo ' - '; } /** @@ -443,26 +344,22 @@ function template_retry_activate() { // Just ask them for their code so they can try it again... echo ' -
    -
    -

    ', Utils::$context['page_title'], '

    -
    -
    -
    '; +
    +

    ', Utils::$context['page_title'], '

    +
    + '; // You didn't even have an ID? if (empty(Utils::$context['member_id'])) { echo ' -
    ', Lang::getTxt('invalid_activation_username', file: 'Login'), '
    -
    '; + +
    '; } echo ' -
    ', Lang::getTxt('invalid_activation_retry', file: 'Login'), '
    -
    -
    -

    -
    + +
    +
    '; } @@ -473,35 +370,27 @@ function template_resend() { // Just ask them for their code so they can try it again... echo ' -
    -
    -

    ', Utils::$context['page_title'], '

    -
    -
    -
    -
    ', Lang::getTxt('invalid_activation_username', file: 'Login'), '
    -
    -
    -

    ', Lang::getTxt('invalid_activation_new', file: 'Login'), '

    -
    -
    ', Lang::getTxt('invalid_activation_new_email', file: 'Login'), '
    -
    -
    ', Lang::getTxt('invalid_activation_password', file: 'Login'), '
    -
    -
    '; +
    +

    ', Utils::$context['page_title'], '

    +
    + + +
    +

    ', Lang::getTxt('invalid_activation_new', file: 'Login'), '

    + +
    + +
    '; if (Utils::$context['can_activate']) { echo ' -

    ', Lang::getTxt('invalid_activation_known', file: 'Login'), '

    -
    -
    ', Lang::getTxt('invalid_activation_retry', file: 'Login'), '
    -
    -
    '; +

    ', Lang::getTxt('invalid_activation_known', file: 'Login'), '

    + +
    '; } echo ' -

    -
    +
    '; } @@ -510,23 +399,20 @@ function template_resend() */ function template_logout() { - // This isn't that much... just like normal login but with a message at the top. echo ' -
    -
    -
    -

    ', Lang::getTxt('logout_confirm', file: 'Login'), '

    -
    -
    -

    - ', Lang::getTxt('logout_notice', file: 'Login'), ' -

    - -

    - - -

    -
    -
    + +
    +

    ', Lang::getTxt('logout_confirm', file: 'Login'), '

    +
    +
    +

    + ', Lang::getTxt('logout_notice', file: 'Login'), ' +

    + +

    + + +

    +
    '; } diff --git a/Themes/default/ManageBoards.template.php b/Themes/default/ManageBoards.template.php index 12cbf4a42f8..269cc076747 100644 --- a/Themes/default/ManageBoards.template.php +++ b/Themes/default/ManageBoards.template.php @@ -70,7 +70,7 @@ function template_main() // List through every board in the category, printing its name and link to modify the board. foreach ($category['boards'] as $board) { echo ' - + ', $board['name'], '', !empty(Config::$modSettings['recycle_board']) && !empty(Config::$modSettings['recycle_enable']) && Config::$modSettings['recycle_board'] == $board['id'] ? $recycle_board : '', $board['is_redirect'] ? $redirect_board : '', ' ', Utils::$context['can_manage_permissions'] ? '' . Lang::getTxt('mboards_permissions', file: 'ManageBoards') . '' : '', ' @@ -81,7 +81,7 @@ function template_main() if (!empty($board['move_links'])) { echo ' -
  • '; +
  • '; foreach ($board['move_links'] as $link) { echo ' diff --git a/Themes/default/ManageMaintenance.template.php b/Themes/default/ManageMaintenance.template.php index a3a7f4f792c..c7a9d43f727 100644 --- a/Themes/default/ManageMaintenance.template.php +++ b/Themes/default/ManageMaintenance.template.php @@ -64,10 +64,12 @@ function template_maintain_database() echo '

    ', Lang::getTxt('entity_convert_title', file: 'ManageMaintenance'), '

    +

    ', Lang::getTxt('maintain_convertentities_title', file: 'ManageMaintenance'), '

    ', Lang::getTxt('entity_convert_introduction', file: 'ManageMaintenance'), '

    +

    ', Lang::getTxt('maintain_convertentities_introduction', file: 'ManageMaintenance'), '

    @@ -79,10 +81,7 @@ function template_maintain_database()
    '; } -/** - * Template for the routine maintenance tasks. - */ -function template_maintain_routine() +function template_maintain_above() { // Starts off with general maintenance procedures. echo ' @@ -95,84 +94,36 @@ function template_maintain_routine() ', Lang::getTxt('maintain_done', ['task' => Utils::$context['maintenance_finished']], file: 'Admin'), '
  • '; } +} +function template_maintain_below() +{ echo ' -
    -

    ', Lang::getTxt('maintain_version', file: 'ManageMaintenance'), '

    -
    -
    - -

    - ', Lang::getTxt('maintain_version_info', file: 'ManageMaintenance'), ' - - -

    - -
    -
    -

    ', Lang::getTxt('maintain_errors', file: 'ManageMaintenance'), '

    -
    -
    -
    -

    - ', Lang::getTxt('maintain_errors_info', file: 'ManageMaintenance'), ' - - - -

    -
    -
    -
    -

    ', Lang::getTxt('maintain_recount', file: 'ManageMaintenance'), '

    -
    -
    -
    -

    - ', Lang::getTxt('maintain_recount_info', file: 'ManageMaintenance'), ' - - - -

    -
    -
    -
    -

    ', Lang::getTxt('maintain_rebuild_settings', file: 'ManageMaintenance'), '

    -
    -
    -
    -

    - ', Lang::getTxt('maintain_rebuild_settings_info', file: 'ManageMaintenance'), ' - - -

    -
    -
    -
    -

    ', Lang::getTxt('maintain_logs', file: 'ManageMaintenance'), '

    -
    -
    -
    -

    - ', Lang::getTxt('maintain_logs_info', file: 'ManageMaintenance'), ' - - - -

    -
    -
    -
    -

    ', Lang::getTxt('maintain_cache', file: 'ManageMaintenance'), '

    -
    -
    -
    -

    - ', Lang::getTxt('maintain_cache_info', file: 'ManageMaintenance'), ' - - - -

    -
    -
    + '; +} + +/** + * Template for the routine maintenance tasks. + */ +function template_maintain_options() +{ + echo ' +
    '; + + foreach (Utils::$context['options'] as $option => $val) { + echo ' +
    '; + } + + echo ' + + + +
    '; } @@ -345,15 +296,13 @@ function checkAttributeValidity() '; } @@ -371,32 +320,6 @@ function template_maintain_topics() '; } - // Bit of javascript for showing which boards to prune in an otherwise hidden list. - echo ' - '; - echo '
    @@ -419,46 +342,13 @@ function swapRot()


    -

    - + ', Lang::getTxt('maintain_old_all', file: 'ManageMaintenance'), ' -

    - + @@ -575,12 +465,12 @@ function template_convert_entities() echo '
    -

    ', Lang::getTxt('entity_convert_title', file: 'ManageMaintenance'), '

    +

    ', Lang::getTxt('maintain_convertentities_title', file: 'ManageMaintenance'), '

    -

    ', Lang::getTxt('entity_convert_introduction', file: 'ManageMaintenance'), '

    +

    ', Lang::getTxt('maintain_convertentities_introduction', file: 'ManageMaintenance'), '

    - +
    '; diff --git a/Themes/default/ManageNews.template.php b/Themes/default/ManageNews.template.php index aa39082a84a..75b5126e270 100644 --- a/Themes/default/ManageNews.template.php +++ b/Themes/default/ManageNews.template.php @@ -303,8 +303,6 @@ function previewPost() return false; } - else - return submitThisOnce(document.forms.newsmodify); } function onDocSent(XMLDoc) { @@ -392,7 +390,7 @@ function template_email_members_send()

    - + @@ -413,9 +411,9 @@ function template_email_members_send()
    - '; + '; } /** diff --git a/Themes/default/ManagePaid.template.php b/Themes/default/ManagePaid.template.php index c14b7d01b26..1abe7f1bb0b 100644 --- a/Themes/default/ManagePaid.template.php +++ b/Themes/default/ManagePaid.template.php @@ -215,12 +215,6 @@ function template_delete_subscription() */ function template_modify_user_subscription() { - // Some quickly stolen javascript from Post, could do with being more efficient :) - echo ' - '; - echo '
    @@ -263,17 +257,17 @@ function template_modify_user_subscription()
    ', Lang::getTxt('start_date_and_time', file: 'ManagePaid'), ' - '; // Show a list of all the years we allow... - for ($year = 2005; $year <= 2030; $year++) { + for ($cur_year = idate('Y'), $year = min($cur_year - 10, Utils::$context['sub']['start']['year']); $year <= $cur_year + 10; $year++) { echo ' '; } echo ' - '; // There are 12 months per year - ensure that they all get listed. for ($month = 1; $month <= 12; $month++) { @@ -300,17 +294,17 @@ function template_modify_user_subscription()
    ', Lang::getTxt('end_date_and_time', file: 'ManagePaid'), ' - '; // Show a list of all the years we allow... - for ($year = 2005; $year <= 2030; $year++) { + for ($cur_year = idate('Y'), $year = min($cur_year - 10, Utils::$context['sub']['end']['year']); $year <= $cur_year + 10; $year++) { echo ' '; } echo ' - '; // There are 12 months per year - ensure that they all get listed. for ($month = 1; $month <= 12; $month++) { @@ -348,6 +342,10 @@ function template_modify_user_subscription() sSearchType: \'member\', sTextDeleteItem: \'', Lang::getTxt('autosuggest_delete_item', file: 'General'), '\', }); + document.getElementById("year").addEventListener("change", generateDays); + document.getElementById("month").addEventListener("change", generateDays); + document.getElementById("yearend").addEventListener("change", generateDays.bind(null, "end")); + document.getElementById("monthend").addEventListener("change", generateDays.bind(null, "end")); '; if (!empty(Utils::$context['pending_payments'])) { diff --git a/Themes/default/ManagePermissions.template.php b/Themes/default/ManagePermissions.template.php index 080a3b42632..28d0df0103c 100644 --- a/Themes/default/ManagePermissions.template.php +++ b/Themes/default/ManagePermissions.template.php @@ -119,9 +119,9 @@ function template_permission_index() echo '
    diff --git a/Themes/default/ManageSearch.template.php b/Themes/default/ManageSearch.template.php index 2925b001021..92615ba0c02 100644 --- a/Themes/default/ManageSearch.template.php +++ b/Themes/default/ManageSearch.template.php @@ -323,7 +323,7 @@ function template_create_index_progress()

    - + @@ -333,7 +333,6 @@ function template_create_index_progress() '; - } /** diff --git a/Themes/default/Memberlist.template.php b/Themes/default/Memberlist.template.php index 1fdfdceb159..7b1239d0b7a 100644 --- a/Themes/default/Memberlist.template.php +++ b/Themes/default/Memberlist.template.php @@ -23,8 +23,8 @@ function template_main() echo '
    - ', template_button_strip(Utils::$context['memberlist_buttons'], 'right'), ' + ', template_button_strip(Utils::$context['memberlist_buttons'], 'right'), '

    diff --git a/Themes/default/MessageIndex.template.php b/Themes/default/MessageIndex.template.php index cab48736601..b8411b6a1f5 100644 --- a/Themes/default/MessageIndex.template.php +++ b/Themes/default/MessageIndex.template.php @@ -22,7 +22,7 @@ */ function template_main() { - echo '
    + echo '

    ', Utils::$context['name'], '

    '; if (isset(Utils::$context['description']) && Utils::$context['description'] != '') { @@ -74,46 +74,15 @@ function template_main() if (!empty(Utils::$context['boards']) && (!empty(Theme::$current->options['show_children']) || Utils::$context['start'] == 0)) { echo ' -

    ', Lang::getTxt('sub_boards', file: 'General'), '

    -
    '; - - foreach (Utils::$context['boards'] as $board) { - echo ' -
    -
    - ', function_exists('template_bi_' . $board['type'] . '_icon') ? call_user_func('template_bi_' . $board['type'] . '_icon', $board) : template_bi_board_icon($board), ' -
    -
    - ', function_exists('template_bi_' . $board['type'] . '_info') ? call_user_func('template_bi_' . $board['type'] . '_info', $board) : template_bi_board_info($board), ' -
    '; - - // Show some basic information about the number of posts, etc. - echo ' -
    - ', function_exists('template_bi_' . $board['type'] . '_stats') ? call_user_func('template_bi_' . $board['type'] . '_stats', $board) : template_bi_board_stats($board), ' -
    '; - - // Show the last post if there is one. - echo ' -
    - ', function_exists('template_bi_' . $board['type'] . '_lastpost') ? call_user_func('template_bi_' . $board['type'] . '_lastpost', $board) : template_bi_board_lastpost($board), ' -
    '; - - // Won't somebody think of the children! - if (function_exists('template_bi_' . $board['type'] . '_children')) { - call_user_func('template_bi_' . $board['type'] . '_children', $board); - } else { - template_bi_board_children($board); - } +
    +
    '; - echo ' -
    '; - } + template_list_boards(Utils::$context['boards']); echo ' -
    '; +
    '; } // Let them know why their message became unapproved. @@ -159,148 +128,7 @@ function template_main() '; } - echo ' -
    '; - - echo ' -
    '; - - // Are there actually any topics to show? - if (!empty(Utils::$context['topics'])) { - echo ' -
    -
    ', Utils::$context['topics_headers']['subject'], ' / ', Utils::$context['topics_headers']['starter'], '
    -
    ', Utils::$context['topics_headers']['replies'], ' / ', Utils::$context['topics_headers']['views'], '
    -
    ', Utils::$context['topics_headers']['last_post'], '
    '; - - // Show a "select all" box for quick moderation? - if (!empty(Utils::$context['can_quick_mod']) && Theme::$current->options['display_quick_mod'] == 1) { - echo ' -
    - -
    '; - } - - // If it's on in "image" mode, don't show anything but the column. - elseif (!empty(Utils::$context['can_quick_mod'])) { - echo ' -
    '; - } - } - - // No topics... just say, "sorry bub". - else { - echo ' -

    ', Lang::getTxt('topic_alert_none', file: 'General'), '

    '; - } - - echo ' -
    '; - - // Contain the topic list - echo ' -
    '; - - foreach (Utils::$context['topics'] as $topic) { - echo ' -
    -
    - - ', $topic['is_posted_in'] ? '' : '', ' -
    -
    -
    '; - - // Now we handle the icons - echo ' -
    '; - - if ($topic['is_watched']) { - echo ' - '; - } - - if ($topic['is_locked']) { - echo ' - '; - } - - if ($topic['is_sticky']) { - echo ' - '; - } - - if ($topic['is_redirect']) { - echo ' - '; - } - - if ($topic['is_poll']) { - echo ' - '; - } - - echo ' -
    '; - - echo ' -
    - ', $topic['new'] && User::$me->is_logged ? '' . Lang::getTxt('new', file: 'General') . '' : '', ' - - ', $topic['first_post']['link'], (!$topic['approved'] ? ' (' . Lang::getTxt('awaiting_approval', file: 'General') . ')' : ''), ' - -
    -

    - ', Lang::getTxt('started_by_member', ['member' => $topic['first_post']['member']['link']], file: 'General'), ' -

    - ', !empty($topic['pages']) ? '' . $topic['pages'] . '' : '', ' -
    -
    -
    -

    ', Lang::getTxt('number_of_replies', [$topic['replies']], file: 'General'), '
    ', Lang::getTxt('number_of_views', [$topic['views']], file: 'General'), '

    -
    -
    -

    ', Lang::getTxt('last_post_topic', ['post_link' => '' . $topic['last_post']['time'] . '', 'member_link' => $topic['last_post']['member']['link']], file: 'General'), '

    -
    '; - - // Show the quick moderation options? - if (!empty(Utils::$context['can_quick_mod'])) { - echo ' -
    '; - - if (Theme::$current->options['display_quick_mod'] == 1) { - echo ' - '; - } else { - // Check permissions on each and show only the ones they are allowed to use. - if ($topic['quick_mod']['remove']) { - echo ''; - } - - if ($topic['quick_mod']['lock']) { - echo ''; - } - - if ($topic['quick_mod']['lock'] || $topic['quick_mod']['remove']) { - echo '
    '; - } - - if ($topic['quick_mod']['sticky']) { - echo ''; - } - - if ($topic['quick_mod']['move']) { - echo ''; - } - } - echo ' -
    '; - } - echo ' -
    '; - } - echo ' -
    '; + template_list_topics(Utils::$context['topics_headers'], Utils::$context['topics']); if (!empty(Utils::$context['can_quick_mod']) && Theme::$current->options['display_quick_mod'] == 1 && !empty(Utils::$context['topics'])) { echo ' @@ -329,10 +157,6 @@ function template_main()
    '; } - echo ' -
    '; - - // Finish off the form - again. if (!empty(Utils::$context['can_quick_mod']) && Theme::$current->options['display_quick_mod'] > 0 && !empty(Utils::$context['topics'])) { echo ' @@ -341,12 +165,12 @@ function template_main() echo '
    - ', template_button_strip(Utils::$context['normal_buttons'], 'right'), ' ', Utils::$context['menu_separator'], ' '; +
    + ', template_button_strip(Utils::$context['normal_buttons'], 'right'), ''; // Mobile action buttons (bottom) if (!empty(Utils::$context['normal_buttons'])) { @@ -363,13 +187,13 @@ function template_main() // Show breadcrumbs at the bottom too. theme_linktree(); - echo ' + echo ' '; - if (!empty(Utils::$context['poll_error']['messages'])) { echo '
    @@ -58,7 +37,7 @@ function addPollOption() // Start the main poll form. echo '
    - +

    ', Utils::$context['page_title'], '

    '; @@ -69,8 +48,8 @@ function addPollOption()
    ', Lang::getTxt('poll_question', file: 'General'), ' -
    ', Lang::getTxt('poll_question', file: 'General'), '
    +
    '; foreach (Utils::$context['choices'] as $choice) { @@ -91,9 +70,7 @@ function addPollOption() } echo ' -

    - (', Lang::getTxt('poll_add_option', file: 'Post'), ')
    ', Lang::getTxt('poll_options', file: 'Post'), ' @@ -109,7 +86,7 @@ function addPollOption()

    - ', Lang::getTxt('poll_run_limit', file: 'Post'), ' + ', Lang::getTxt('poll_run_limit', file: 'Post'), '
    @@ -124,7 +101,7 @@ function addPollOption() if (Utils::$context['poll']['guest_vote_allowed']) { echo '
    - +
    @@ -134,7 +111,7 @@ function addPollOption() echo '
    - ', Lang::getTxt('poll_results_visibility', file: 'Post'), ' + ', Lang::getTxt('poll_results_visibility', file: 'Post'), ':

    @@ -153,7 +130,7 @@ function addPollOption()
    '; } echo ' - +
    diff --git a/Themes/default/Post.template.php b/Themes/default/Post.template.php index d168725fc12..2627705a964 100644 --- a/Themes/default/Post.template.php +++ b/Themes/default/Post.template.php @@ -11,7 +11,6 @@ * @version 3.0 Alpha 4 */ -use SMF\BrowserDetector; use SMF\Config; use SMF\Lang; use SMF\Theme; @@ -27,38 +26,10 @@ function template_main() echo ' '; // If the user is replying to a topic show the previous posts. @@ -595,36 +531,34 @@ function addPollOption() } echo ' -
    -
    -
    -
    - ', Lang::getTxt('posted_by_member_time', ['member' => $post['poster'], 'time' => $post['time']], file: 'General'), ' -
    -
    '; +
    +
    +
    + ', Lang::getTxt('posted_by_member_time', ['member' => $post['poster'], 'time' => $post['time']]), ' +
    +
    '; if ($ignoring) { echo ' -
    - ', Lang::getTxt('ignoring_user', file: 'General'), ' - -
    '; +
    + ', Lang::getTxt('ignoring_user', file: 'General'), ' + +
    '; } echo ' -
    ', Utils::adjustHeadingLevels($post['message'], 5), '
    '; +
    ', Utils::adjustHeadingLevels($post['message'], 5), '
    '; if (Utils::$context['can_quote']) { echo ' - '; + '; } echo ' -
    -
    '; +
    '; } echo ' @@ -652,20 +586,55 @@ function addPollOption() } echo ' - function insertQuoteFast(messageid) - { - var e = document.getElementById("', Utils::$context['post_box_name'], '"); - sceditor.instance(e).insertQuoteFast(messageid); + '; + } +} - return true; - } - function onReceiveOpener(text) - { - var e = document.getElementById("', Utils::$context['post_box_name'], '"); - sceditor.instance(e).insert(text); +/** + * Render additional post options as a list of checkboxes. + * + * $additional_options is an array of options to be rendered as elements in a HTML unordered list. It accrpts: + * - 'can_show' (bool): Whether the option should be displayed (based on user permissions). + * - 'name' (string): The name attribute of the checkbox input. + * - 'id' (string): The id attribute of the checkbox input. + * - 'checked' (bool): Whether the checkbox should be pre-checked or not. + * - 'label' (string): The label text displayed next to the checkbox. + * - 'value' (string, optional): The value attribute of the checkbox (default is '1'). + * - 'hidden' (array, optional): Any hidden input fields associated with this option (key-value pairs). + * + * @param array $additional_options The options array containing the settings for each checkbox. + * @param ?string $id The id for the wrapper list element (`ul`). + */ +function template_additional_options(array $additional_options, ?string $id = null): void +{ + echo ' +
      '; + + foreach ($additional_options as $option) { + // Only display the option if can_show is true + if ($option['can_show']) { + echo ' +
    • '; + + // Render hidden fields if present + if (isset($option['hidden'])) { + foreach ($option['hidden'] as $hidden_name => $hidden_value) { + echo ' + '; + } } - '; + + // Render the checkbox input + echo ' + +
    • '; + } } + + echo ' +
    '; } /** @@ -858,7 +827,7 @@ function template_announcement_send()


    - + @@ -872,7 +841,6 @@ function template_announcement_send()

    '; } diff --git a/Themes/default/Profile.template.php b/Themes/default/Profile.template.php index 1354aeab21e..74874230276 100644 --- a/Themes/default/Profile.template.php +++ b/Themes/default/Profile.template.php @@ -11,11 +11,11 @@ * @version 3.0 Alpha 4 */ -use SMF\BrowserDetector; use SMF\Config; use SMF\Lang; use SMF\Security; use SMF\Theme; +use SMF\Time; use SMF\User; use SMF\Utils; @@ -24,14 +24,6 @@ */ function template_profile_above() { - // Prevent Chrome from auto completing fields when viewing/editing other members profiles - if (BrowserDetector::isBrowser('is_chrome') && !User::$me->is_owner) { - echo ' - '; - } - // If an error occurred while trying to save previously, give the user a clue! echo ' ', template_error_message(); @@ -58,15 +50,20 @@ function template_profile_popup() // Unlike almost every other template, this is designed to be included into the HTML directly via $().load() echo ' - -