From 3b0a5ee555d86358dcc0b3f2c8fa24ef2b77ca90 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Mon, 21 Oct 2024 09:35:53 +0100 Subject: [PATCH 01/29] Added new JS folder to hold scripts Moved body scripts to a newly created JS file, located at /js/scripts2024.js --- _layouts/default.html | 17 +---------------- _layouts/page.html | 12 ------------ js/scripts2024.js | 24 ++++++++++++++++++++++++ 3 files changed, 25 insertions(+), 28 deletions(-) create mode 100644 js/scripts2024.js diff --git a/_layouts/default.html b/_layouts/default.html index 77348c4..66b2132 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -29,21 +29,6 @@ {% include footer.html %} - + diff --git a/_layouts/page.html b/_layouts/page.html index b308436..d8e6a9e 100644 --- a/_layouts/page.html +++ b/_layouts/page.html @@ -11,15 +11,3 @@ {{ content }} - - diff --git a/js/scripts2024.js b/js/scripts2024.js new file mode 100644 index 0000000..52900ca --- /dev/null +++ b/js/scripts2024.js @@ -0,0 +1,24 @@ +window.addEventListener('load', stickyHeader); +window.addEventListener('scroll', stickyHeader, {passive: true}); + +// Get the offset position of the navbar +var header = document.querySelector('header.header'); +var stickyPos = header ? header.offsetTop : 0; + +function stickyHeader() { + if (window.scrollY > stickyPos) { + header.classList.add('sticky'); + } else { + header.classList.remove('sticky'); + } +} + +// toggle sub-menu +var menuToggle = document.querySelector('.page-sidebar-toggle'); +menuToggle.addEventListener('click', function(ev) { + var nav = this.nextElementSibling; + if (nav.classList.contains('expanded')) + nav.classList.remove('expanded'); + else + nav.classList.add('expanded'); +}); From 0eab8b13eb10a2788308ca7e284b60ee3a31b271 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Mon, 21 Oct 2024 09:47:36 +0100 Subject: [PATCH 02/29] Check Repositories last update Added a Javascript JSON fetch, to display the "date" of each Repository when they were last updated. https://i.ibb.co/TWhmkD0/z581.jpg --- _layouts/default.html | 46 +++++++++++++-------------- addons/plugins.md | 17 ++++++---- addons/themes.md | 4 +++ css/styles2020.css | 41 ++++++++++++++++++++++++ js/scripts2024.js | 74 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 150 insertions(+), 32 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 66b2132..c7b4bd4 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -1,34 +1,34 @@ - - - - + + + + - - - - + + + + - {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title }}{% endif %} - + {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title }}{% endif %} + - - - - {% include analytics.html %} - + + + + {% include analytics.html %} + - + - {% include header.html %} + {% include header.html %} -
- {{ content }} -
+
+ {{ content }} +
- {% include footer.html %} + {% include footer.html %} - - + + diff --git a/addons/plugins.md b/addons/plugins.md index 0ad791e..117fe7f 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -8,6 +8,9 @@ title: "Question2Answer Add-ons - Plugins" To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `qa-plugin` directory, then open the Plugins section of the Admin panel and check it is listed. The plugin may also offer some options on this page. +> 🟢 Status: up-to-date | 🔵 Status: Likely up-to-date | 🟡 Status: Hasn't been updated in a while | 🔘 Status: Inactive for a while | 🔴 Status: Unknown. +> Note that "*Inactive*" & "*Unknown*" plugins doesn't necessarily mean that they do not work. Some plugins just don't require to be updated as frequently. + ## Major features - [Badges Plugin](https://github.com/NoahY/q2a-badges) by [NoahY](http://www.question2answer.org/qa/user/NoahY) ➔ [updated](https://github.com/rxchun/q2a-badges/) by [Chun](https://rxchun.github.io/). Provides user badges which recognize users' activities and achievements. @@ -23,7 +26,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Embed Youtube Plugin](https://github.com/NoahY/q2a-embed) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Converts links to YouTube videos into embedded videos when displaying questions, answers or comments. - [Express Editor](https://github.com/amiyasahu/q2a-express-editor) by [Amiya Sahu](http://amiyasahu.com/). Recent version of [CKEditor](http://ckeditor.com/) bundled with [Ace Editor](http://ace.c9.io/) for smooth code editing. - [Lightbox-images](https://github.com/ProThoughts/q2apro-lightbox-images) by [q2apro.com](https://github.com/q2apro). Provides a Lightbox Effect for all images in posts. -- [Markdown Editor Plugin](https://github.com/ProThoughts/markdown-editor-for-q2a) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/), updated for Q2A 1.8.6 [here](https://github.com/fardina/markdown-editor-for-q2a). +- [Markdown Editor Plugin](https://github.com/ProThoughts/markdown-editor-for-q2a) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/), ➔ updated for Q2A 1.8.6 [here](https://github.com/fardina/markdown-editor-for-q2a). - [Memes for Text](https://github.com/thibaultduponchelle/q2a-memes) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). Post *lolfaces* in your answers or comments. - [Miranda](http://www.question2answer.org/qa/51849/tinymcewrapper-miranda-updated-most-powerful-editor-joins) [Tinymcse Wrapper](https://www.tinymce.com/) by Don Shakespeare. Adds Tinymcse to be used across posts as well as other text areas like messages, wall posts and is higly customizable. - [Prevent Simultaneous Edits](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [q2apro.com](https://github.com/q2apro). Prevent two users editing the same post simultaneously. Also [forked](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [Jalal Jaberi](https://github.com/ElephantsGroup). @@ -31,7 +34,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Shortcode Plugin](https://github.com/NoahY/q2a-shortcode) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows posts to be displayed with text substitutions, including support for regular expressions. - [Smilies for Text](https://github.com/NoahY/q2a-smilies) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows smilies to be embedded in plain text or Markdown content. Also [forked](https://github.com/thibaultduponchelle/q2a-smilies) by [Thibault Duponchelle](https://github.com/thibaultduponchelle) to support multiboxes, add a lot of smilies and slightly improve the style. - [Syntax Highlighter](https://github.com/amiyasahu/q2a-syntax-highlighter) by [Amiya Sahu](http://amiyasahu.com/). Code syntax highlighting based on [highlight.js](https://highlightjs.org/) with over 40 built-in themes. -- [WYSIWYG Math Editor](https://github.com/thibaultduponchelle/q2a-wysiwyg-matheditor) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). A CKEditor 4 editor plugin *with math capabilities*. Allow users to add math formulas (MathJax) in questions, answers and comments. Other math editors based on *preview zone* like [SCEditor](https://github.com/ProThoughts/q2apro-sceditor-free)were already mentioned above. Also: [q2a-formatter](https://github.com/tangruize/q2a-formatter) (or [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) from [Arjun Suresh](https://github.com/arjunsuresh) from which [q2a-formatter](https://github.com/tangruize/q2a-formatter) was forked). +- [WYSIWYG Math Editor](https://github.com/thibaultduponchelle/q2a-wysiwyg-matheditor) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). A CKEditor 4 editor plugin *with math capabilities*. Allow users to add math formulas (MathJax) in questions, answers and comments. Other math editors based on *preview zone* like [SCEditor](https://github.com/ProThoughts/q2apro-sceditor-free) were already mentioned above. Also: [q2a-formatter](https://github.com/tangruize/q2a-formatter) (or [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) from [Arjun Suresh](https://github.com/arjunsuresh) from which [q2a-formatter](https://github.com/tangruize/q2a-formatter) was forked). - [Paid] [DM - Dynamic Mentions](https://www.question2answer.org/qa/66929) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows users to be mentioned using the @user syntax. @@ -59,7 +62,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Mattermost Notifications](https://github.com/Schoaf/qa-mattermost-notifications) by [Andreas Scharf](https://github.com/Schoaf). Sends notifications of new questions to a [Mattermost](http://mattermost.org/) feed. - [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) by [Arjun](http://gateoverflow.in/user/Arjun). Handles the trouble of adding MathJax or CodePretiffy to Q2A. - [MyBB single sign-on](http://community.mybb.com/thread-121885-post-879492.html#pid879492) by Dahevos. Allows Q2A to be integrated into a MyBB forum, sharing the same user database. -- [Open Login](https://github.com/alixandru/q2a-open-login) by [Alex Lixandru](https://github.com/alixandru/). Log in via [Facebook](http://www.facebook.com/), [Google](http://www.google.com/), [Yahoo](http://www.yahoo.com/), [Github](http://www.github.com/) and many more, plus multiple logins for a single Q2A account. +- [Open Login](https://github.com/alixandru/q2a-open-login) by [Alex Lixandru](https://github.com/alixandru). Log in via [Facebook](http://www.facebook.com/), [Google](http://www.google.com/), [Yahoo](http://www.yahoo.com/), [Github](http://www.github.com/) and many more, plus multiple logins for a single Q2A account. - [Persian Magic Words](http://question2answer-farsi.com/707/q2a-magicword-plugin.html) by AmirHossein Tavousi @ [Question2Answer Farsi](http://question2answer-farsi.com/). Shows word meanings from [Vajehyab.com](http://vajehyab.com/) for highlighted words. - [Proxy SSO](https://github.com/larrykluger/Question-2-Answer-Proxy-SSO-Plugin) by Larry Kluger. Lets your main website serve as a single sign-on authentication system for Q2A. - [uLogin Login Plugin](https://github.com/saxap/ulogin-for-q2a) by [Sergey Astafev](http://dontforget.pro/). Allows logging in via [uLogin](http://ulogin.ru), which supports many identity providers popular in Russia. @@ -97,7 +100,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Online User Count](https://github.com/pupi1985/q2a-onlineusers/tree/a1e555b77b5a0585f8c336d9e37e1e4a2bece437) by Ali Sayahiyan. Shows a list of users who are currently online as well as total visitors. Also [forked here](https://github.com/pupi1985/q2a-onlineusers) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). - [Popular Questions](https://github.com/ProThoughts/q2apro-popular-questions-widget) by [q2apro.com](https://github.com/q2apro). Shows a list of the most viewed questions. - [Random Question Widget](https://github.com/NoahY/qa-random-question) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Displays a random question in the sidebar. -- [Recent Events Widget](https://github.com/fullstack412/Q2A-Recent-Events-Widget) by [q2apro.com](https://github.com/q2apro/). Displays all recent events in the sidebar or main area. +- [Recent Events Widget](https://github.com/fullstack412/Q2A-Recent-Events-Widget) by [q2apro.com](https://github.com/q2apro). Displays all recent events in the sidebar or main area. - [Recent Questions Widget](http://www.q2amarket.com/market/q2am-recent-questions/) by [Q2A Market](http://www.q2amarket.com/). Shows a list of recent questions in the sidebar or other locations. - [RSS Feed Widget](https://github.com/mbentley3123/q2a-rss-feed) by Mark Bentle by Mark Bentleyy. Displays posts from an RSS feed in the sidebar. - [Simple Slider](https://github.com/JacksiroKe/q2a-simple-slider) by [Jack Siro](https://www.linkedin.com/in/jacksiroke/). A simple image slider for the tops of pages, showing titles, descriptions and links. @@ -106,7 +109,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Tag List Widget](https://github.com/svivian/q2a-tag-list-widget) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). Displays the most popular tags in a simple list. - [Twitter Widget](https://github.com/Towhidn/q2a-twitter) by [Towhid](http://TowhidN.com/). Shows recent tweets from your twitter account. - [Widget Anywhere Plugin](https://github.com/svivian/q2a-widget-anywhere) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). Allows custom content to be added on any page, in a variety of locations. -- [Simple Ads Manager](https://github.com/ProThoughts/q2a-simple-ads-manager) by [ProThoughts](http://ProThoughts.com). Allow you to add advertisements in [listed spots](https://github.com/ProThoughts/q2a-simple-ads-manager). You can use Google adsense or any HTML ad code. Also [forked](https://github.com/arjunsuresh/q2a-simple-ads-manager) by [Arjun](http://gateoverflow.in/user/Arjun) to add more options like Ads after first question hiding ads for certain user level and above etc. +- [Simple Ads Manager](https://github.com/ProThoughts/q2a-simple-ads-manager) by [ProThoughts](http://ProThoughts.com). Allow you to add advertisements in listed spots. You can use Google adsense or any HTML ad code. Also [forked](https://github.com/arjunsuresh/q2a-simple-ads-manager) by [Arjun](http://gateoverflow.in/user/Arjun) to add more options like Ads after first question hiding ads for certain user level and above etc. - [Paid] [AS - Advanced Search](https://www.question2answer.org/qa/54072) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Extends the way in which Q2A searches for posts and allows the user to get better results when searching. ## User interface @@ -141,8 +144,8 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Theme Switcher Plugin](https://github.com/arjunsuresh/q2a-theme-switch) by [NoahY](http://www.question2answer.org/qa/user/NoahY) and updated by [Arjun](http://gateoverflow.in/user/Arjun). Allows users to choose their own theme on their profile page for normal as well as mobile view. - [Top Searches](https://github.com/arjunsuresh/q2a-top-search) by [Arjun](http://gateoverflow.in/user/Arjun). Adds a widget to display the top searched words. Also have an option to display top seacrhed tags if using [Tag Search](https://github.com/arjunsuresh/tag-search) plugin. - [User Signatures Plugin](https://github.com/NoahY/q2a-signatures) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to define a signature for all of their posts. Also [forked here](https://github.com/pupi1985/q2a-signatures) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). -- [Userinfo](https://github.com/ProThoughts/q2apro-userinfo) by [q2apro.com](https://github.com/q2apro).Mouse over a username to display user profile information: Avatar image, account age, total points, monthly points, answers, best answers, ratio, questions posted, badges. -- [Userslist-locations](https://github.com/ProThoughts/q2apro-userslist-locations) by [q2apro.com](https://github.com/q2apro).Add Location to users list +- [Userinfo](https://github.com/ProThoughts/q2apro-userinfo) by [q2apro.com](https://github.com/q2apro). Mouse over a username to display user profile information: Avatar image, account age, total points, monthly points, answers, best answers, ratio, questions posted, badges. +- [Userslist-locations](https://github.com/ProThoughts/q2apro-userslist-locations) by [q2apro.com](https://github.com/q2apro). Add Location to users list - [Warn on Leave](https://github.com/yshiga/q2apro-warn-on-leave) by [q2apro.com](https://github.com/q2apro). Warns users before leaving a page with text they have entered (also support WYSIWYG editor). - [MTL - Maximum Tag Length](https://www.question2answer.org/qa/41822) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Limits the amount of characters allowed per each question tag. - [RA - Random Avatar](https://www.question2answer.org/qa/39812) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Assigns new users a random avatar from a given set immediately after registering. diff --git a/addons/themes.md b/addons/themes.md index 3ea3c67..3a40a8f 100644 --- a/addons/themes.md +++ b/addons/themes.md @@ -8,6 +8,10 @@ title: "Question2Answer Add-ons - Themes" To install a theme, place its directory in Q2A's `qa-theme` directory, then open the General section of the Admin panel and choose the theme from the menu provided. **Many good themes can be found at [Q2A service providers](/services/)**. Below is a list of other recently updated themes: +> 🟢 Status: up-to-date | 🔵 Status: Likely up-to-date | 🟡 Status: Hasn't been updated in a while | 🔘 Status: Inactive for a while | 🔴 Status: Unknown. +> Note that "*Inactive*" & "*Unknown*" themes doesn't necessarily mean that they do not work. Some themes just don't require to be updated as frequently. + + - [MayroPro](https://github.com/MominRaza/MayroPro) (A Google Material Design Theme with Dark Mode, RTL, PWA Support) by [Momin Raza](https://mominraza.github.io). - [Blog Theme](https://github.com/ostack/qa-ostack-blog-theme) by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146) based on SnowFlat. [Demo](https://www.ostack.cn). This theme can show pictures and content in question lists, to make the Q2A site look like a blog. - [FatSnowFlat](https://github.com/ostack/qa-FatSnowFlat-theme) by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146) based on SnowFlat. [Demo](https://www.ostack.cn). This theme can show pictures and content in question lists. diff --git a/css/styles2020.css b/css/styles2020.css index 32cb2a0..4096da3 100644 --- a/css/styles2020.css +++ b/css/styles2020.css @@ -94,6 +94,12 @@ pre code { font-size: 13px; } +blockquote { + background-color: #FFE0B2; + padding: .1rem 1rem; + margin: 0; +} + /* ================== @@ -183,6 +189,41 @@ a.main-nav-link-separate { background-color: #fff; box-shadow: 2px 2px 4px rgba(80, 80, 80, 0.1); } + +#question2answer-plugins ~ ul li, +#question2answer-themes ~ ul li { + border-bottom: 1px solid #e0e0e0; + margin: .3rem 0; +} + +.rep-date { + display: inline-block; + background-color: #e0e0e0; + padding: 0px 6px; + border-radius: 3px; + margin-inline-end: 3px; + margin-bottom: 6px; +} + +.rep-date-0, .rep-date-1 { + background-color: #18ab60; /* Green */ + color: #ffffff; +} +.rep-date-2 { + background-color: #3498db; /* Blue */ + color: #ffffff; +} +.rep-date-3 { + background-color: #ffeb3b; /* Yellow */ +} +.rep-date-4, .rep-date-5 { + background-color: #e0e0e0; /* Grey */ +} +.rep-date-bad { + background-color: #e74c3c; /* Red */ + color: #ffffff; +} + .page-sidebar { padding: 1.5rem 1rem; } diff --git a/js/scripts2024.js b/js/scripts2024.js index 52900ca..a706c63 100644 --- a/js/scripts2024.js +++ b/js/scripts2024.js @@ -14,11 +14,81 @@ function stickyHeader() { } // toggle sub-menu -var menuToggle = document.querySelector('.page-sidebar-toggle'); +let menuToggle = document.querySelector('.page-sidebar-toggle'); menuToggle.addEventListener('click', function(ev) { - var nav = this.nextElementSibling; + let nav = this.nextElementSibling; if (nav.classList.contains('expanded')) nav.classList.remove('expanded'); else nav.classList.add('expanded'); }); + +// Calculate Github repository year gap, against present year +// The gap year will be appended to a class later +const calcDate = param => { + const dateObj = new Date(); + const currentYear = dateObj.getUTCFullYear(); + + const repositoryYear = param.substring(0, param.indexOf('-')); + + let yearGap = 0; + if ( currentYear - repositoryYear === 1){ + yearGap = 1; + } else if ( currentYear - repositoryYear === 2){ + yearGap = 2; + } else if ( currentYear - repositoryYear === 3){ + yearGap = 3; + } else if ( currentYear - repositoryYear >= 4){ + yearGap = 4; + } + + return yearGap; +} + +// Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) +const gitLinks = document.querySelectorAll('.page-content li a[href*="https://github.com/"]'); + +if(gitLinks.length) { + for(let i=0; i { + try { + const response = await fetch(githubJSON); + + // Checks if the Link has more than 4 'forward slashes', aka a repository, to escape Github user profiles + let isRepository = (getGithubHref.match(/\//g) || []).length >= 4; + + if(isRepository){ + if(response.ok){ + let jsonResponse = await response.json(); + // Defines class with the calculated Gap Year calcDate() + const yearGapClass = 'rep-date-' + calcDate(jsonResponse.date); + // Finally prepend the tag/badge + gitLinks[i].insertAdjacentHTML( + 'beforebegin', + `${jsonResponse.date}`, + ); + } else { + // If response is 'bad request' or '404', set an "unknown" tag + gitLinks[i].insertAdjacentHTML( + 'beforebegin', + `Unknown`, + ); + } + } + } + catch(error) { + console.log(error); + } + } + getDates(); + + } +} + + From ffe1e13a39b39859dcd8b107e4950b4704baff5f Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Mon, 18 Nov 2024 03:48:00 +0000 Subject: [PATCH 03/29] Refactored Github Repository dates, finished 2020 design - Refactored the repository dates function. Dates are now stored on user devices via localStorage, and are only fetched again if there's a change on the lists size (one more list item added, one less list item removed) , or 90 days / 3 months have passed since last fetched. the amount of days for the next fetch can be changed at the top of the scripts.js file. - Current visited page now is highlighted. - Added Breadcrumbs - Added sidebar navigation for sections of the articles. - Created a popup for the "Welcome to Docs" text. - Added scroll to top button. - Internal links fixes. - Added Mega Menu for Documentation, to group things into sections. --- Gemfile.lock | 4 + _config.yml | 11 +- _includes/breadcrumbs.html | 17 + _includes/footer.html | 28 +- _includes/header.html | 89 +- _includes/nav-sub.html | 15 - _includes/sidebar.html | 7 + _layouts/default.html | 12 +- _layouts/home.html | 48 +- _layouts/page.html | 11 +- addons/clients.md | 1 + addons/index.md | 9 +- addons/plugins.md | 7 +- addons/themes.md | 7 +- addons/translations-old.md | 1 + addons/translations.md | 7 +- code/external.md | 3 +- code/functions.md | 9 +- code/structure.md | 3 +- contribute/coding-style.md | 1 + contribute/docs.md | 1 + contribute/index.md | 1 + contribute/unit-tests.md | 1 + css/styles.css | 1189 +++++++++++++++++ css/styles2020.css | 546 -------- images/pexels-leeloothefirst-5428824-800.webp | Bin 0 -> 37258 bytes images/question2answer-logo-350x40.webp | Bin 0 -> 5644 bytes images/question2answer-logo-white-350x40.webp | Bin 0 -> 4236 bytes images/question2answer-logo.svg | 3 + images/shadow.png | Bin 52248 -> 0 bytes index.md | 8 +- install/htaccess.md | 1 + install/index.md | 9 +- install/optimize.md | 1 + install/security.md | 3 +- install/single-sign-on.md | 3 +- install/upgrade.md | 3 +- install/versions/1.md | 1 + install/versions/index.md | 5 +- install/wordpress.md | 3 +- js/scripts.js | 471 +++++++ js/scripts2024.js | 94 -- license.md | 1 + plugins/index.md | 43 +- plugins/layers.md | 5 +- plugins/modules-captcha.md | 5 +- plugins/modules-editor.md | 9 +- plugins/modules-event.md | 5 +- plugins/modules-filter.md | 7 +- plugins/modules-login.md | 7 +- plugins/modules-page.md | 7 +- plugins/modules-process.md | 5 +- plugins/modules-search.md | 7 +- plugins/modules-viewer.md | 7 +- plugins/modules-widget.md | 5 +- plugins/modules.md | 27 +- plugins/overrides.md | 3 +- plugins/tutorial.md | 33 +- services.md | 1 + themes/index.md | 3 +- translate/index.md | 7 +- 61 files changed, 1999 insertions(+), 821 deletions(-) create mode 100644 _includes/breadcrumbs.html delete mode 100644 _includes/nav-sub.html create mode 100644 _includes/sidebar.html create mode 100644 css/styles.css delete mode 100644 css/styles2020.css create mode 100644 images/pexels-leeloothefirst-5428824-800.webp create mode 100644 images/question2answer-logo-350x40.webp create mode 100644 images/question2answer-logo-white-350x40.webp create mode 100644 images/question2answer-logo.svg delete mode 100644 images/shadow.png create mode 100644 js/scripts.js delete mode 100644 js/scripts2024.js diff --git a/Gemfile.lock b/Gemfile.lock index 0256451..7216d23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,6 +52,7 @@ GEM faraday-net_http (3.3.0) net-http ffi (1.17.0) + ffi (1.17.0-x64-mingw-ucrt) fiber-annotation (0.2.0) fiber-local (1.1.0) fiber-storage @@ -255,6 +256,8 @@ GEM minitest (5.25.1) net-http (0.4.1) uri + nokogiri (1.16.7-x64-mingw-ucrt) + racc (~> 1.4) nokogiri (1.16.7-x86_64-darwin) racc (~> 1.4) nokogiri (1.16.7-x86_64-linux) @@ -306,6 +309,7 @@ GEM zeitwerk (2.6.18) PLATFORMS + x64-mingw-ucrt x86_64-darwin-22 x86_64-linux diff --git a/_config.yml b/_config.yml index 0837b90..9a77859 100644 --- a/_config.yml +++ b/_config.yml @@ -24,17 +24,18 @@ nav: - name: Code path: /code/external/ sub: code + + second: - name: Contribute path: /contribute/ sub: contribute - name: Add-Ons path: /addons/ sub: addons - - second: + + third: - name: Services url: /services/ - class: main-nav-link-separate - name: Sites url: https://www.question2answer.org/sites.php - name: Q&A @@ -103,12 +104,12 @@ nav: path: /contribute/docs/ addons: - - name: Translations - path: /addons/translations/ - name: Themes path: /addons/themes/ - name: Plugins path: /addons/plugins/ - name: Clients path: /addons/clients/ + - name: Translations + path: /addons/translations/ diff --git a/_includes/breadcrumbs.html b/_includes/breadcrumbs.html new file mode 100644 index 0000000..25b21cd --- /dev/null +++ b/_includes/breadcrumbs.html @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/_includes/footer.html b/_includes/footer.html index 5977d15..adc2a0e 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -1,11 +1,23 @@ +
+
arrow_upward
+
+ +
+ diff --git a/_includes/header.html b/_includes/header.html index e786056..e8362c5 100644 --- a/_includes/header.html +++ b/_includes/header.html @@ -1,15 +1,84 @@ +{% if page.slug == 'homepage' %}
+{% endif %}
-
+ +{% if page.slug != 'homepage' %} + {% include breadcrumbs.html %} +{% endif %} diff --git a/_includes/nav-sub.html b/_includes/nav-sub.html deleted file mode 100644 index 4c2c86b..0000000 --- a/_includes/nav-sub.html +++ /dev/null @@ -1,15 +0,0 @@ - -
Menu
- - diff --git a/_includes/sidebar.html b/_includes/sidebar.html new file mode 100644 index 0000000..9ab86fe --- /dev/null +++ b/_includes/sidebar.html @@ -0,0 +1,7 @@ +
+ +
diff --git a/_layouts/default.html b/_layouts/default.html index c7b4bd4..5f4b888 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -6,20 +6,20 @@ - + - + {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title }}{% endif %} - - + + {% include analytics.html %} - + {% include header.html %} @@ -29,6 +29,6 @@ {% include footer.html %} - + diff --git a/_layouts/home.html b/_layouts/home.html index 7512c12..20c9cd6 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -2,11 +2,23 @@ layout: default --- -

Question2Answer (Q2A) is a popular open source Q&A platform for PHP/MySQL, currently running on 24,500+ sites in 40 languages.

+
+

Question2Answer (Q2A) is a popular open source Q&A platform for PHP/MySQL, currently running on 24,500+ sites in 40 languages.

+
-{{ content }} +
+
+
close
+
+
+ {{ content }} +
+
+
+
+
-
+
{% capture answerText %}

A Q&A site helps your online community to share knowledge. @@ -23,8 +35,8 @@

Question2Answer (Q2A) is a popular open {% include card-showcase.html icon="people" title="Why offer Q&A on my site?" answer=answerText %} {% capture answerText %} -

Download Question2Answer, then read how to install. - Version 1.8.8 was released on July 25th, 2023. +

Download Question2Answer, then read how to install. + Version 1.8.8 was released on July 25th, 2023. Also on GitHub.

Question2Answer is open source, licensed under GPL v2+.

{% endcapture %} @@ -43,9 +55,9 @@

Question2Answer (Q2A) is a popular open {% capture answerText %} @@ -68,24 +80,24 @@

Question2Answer (Q2A) is a popular open
  • Create experts, editors, moderators and admins.
  • User avatars (or Gravatar) and custom fields.
  • Private messages and public wall posts.
  • -
  • Log in via Facebook or others (using plugins).
  • +
  • Log in via Facebook or others (using plugins).
  • {% endcapture %} {% include card-showcase.html icon="face" title="Built-in user account management ..." answer=answerText %} {% capture answerText %}
      -
    • Out-of-the-box WordPress and Joomla integration.
    • -
    • Custom single sign-on support for other sites.
    • -
    • Deep integration and customization via plugins.
    • +
    • Out-of-the-box WordPress and Joomla integration.
    • +
    • Custom single sign-on support for other sites.
    • +
    • Deep integration and customization via plugins.
    {% endcapture %} {% include card-showcase.html icon="assignment_ind" title="... or integrate with existing sites" answer=answerText %} {% capture answerText %}
      -
    • PHP/MySQL scalable to millions of users and posts.
    • -
    • Safe from XSS, CSRF and SQL injection attacks.
    • +
    • PHP/MySQL scalable to millions of users and posts.
    • +
    • Safe from XSS, CSRF and SQL injection attacks.
    • Beat spam with captchas, moderation and/or flagging.
    {% endcapture %} @@ -93,10 +105,10 @@

    Question2Answer (Q2A) is a popular open {% capture answerText %}

    Contribute via Github, - create a theme, - plugin or - translation.

    + create a theme, + plugin or + translation.

    {% endcapture %} {% include card-showcase.html icon="translate" title="How can I help?" answer=answerText %} -

    + diff --git a/_layouts/page.html b/_layouts/page.html index d8e6a9e..1156bc8 100644 --- a/_layouts/page.html +++ b/_layouts/page.html @@ -3,11 +3,10 @@ ---
    -
    - {% include nav-sub.html %} -
    - -
    +
    {{ content }} -
    + +
    diff --git a/addons/clients.md b/addons/clients.md index dc15b3e..a9b0d56 100644 --- a/addons/clients.md +++ b/addons/clients.md @@ -2,6 +2,7 @@ layout: page menu: addons title: "Question2Answer - Add-ons - Clients" +slug: addons-clients --- # Question2Answer Clients diff --git a/addons/index.md b/addons/index.md index 52441f0..de3ca94 100644 --- a/addons/index.md +++ b/addons/index.md @@ -2,17 +2,18 @@ layout: page menu: addons title: "Question2Answer - Add-ons" +slug: addons --- # Question2Answer is being extended by the community. This page links to add-ons created by Question2Answer users. These are not endorsed for quality or suitability, but we hope they are useful. If you have something to contribute, please submit a pull request to [the docs](https://github.com/q2a/q2a.github.io/) or [get in touch](https://www.question2answer.org/feedback.php) - your help is much appreciated! -## [Translations](/addons/translations/) +## [Translations]({{ site.baseurl }}/addons/translations/) -## [Themes](/addons/themes/) +## [Themes]({{ site.baseurl }}/addons/themes/) -## [Plugins](/addons/plugins/) +## [Plugins]({{ site.baseurl }}/addons/plugins/) -## [Clients](/addons/clients/) +## [Clients]({{ site.baseurl }}/addons/clients/) diff --git a/addons/plugins.md b/addons/plugins.md index 117fe7f..7beff74 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -2,6 +2,7 @@ layout: page menu: addons title: "Question2Answer Add-ons - Plugins" +slug: addons-plugins --- # Question2Answer Plugins @@ -140,7 +141,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Social Sharing Plugin](https://github.com/NoahY/q2a-share) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds social sharing buttons to question pages. - [Sort Answers](https://github.com/amiyasahu/q2a-sort-answers) by [Amiya Sahu](http://amiyasahu.com/). Allows answers on a question page to be sorted by oldest, newest and highest voted. - [Tag Search](https://github.com/arjunsuresh/tag-search) by [Arjun](http://gateoverflow.in/user/Arjun). Allows tags to be searched via AJAX and with auto complete. -- [Tag Descriptions Plugin](http://www.question2answer.org/releases/q2a-tag-descriptions-tutorial.zip) from the [plugin tutorial](/plugins/tutorial/). Allows tag descriptions to be shown on tag pages and in tooltips. +- [Tag Descriptions Plugin](http://www.question2answer.org/releases/q2a-tag-descriptions-tutorial.zip) from the [plugin tutorial]({{ site.baseurl }}/plugins/tutorial/). Allows tag descriptions to be shown on tag pages and in tooltips. - [Theme Switcher Plugin](https://github.com/arjunsuresh/q2a-theme-switch) by [NoahY](http://www.question2answer.org/qa/user/NoahY) and updated by [Arjun](http://gateoverflow.in/user/Arjun). Allows users to choose their own theme on their profile page for normal as well as mobile view. - [Top Searches](https://github.com/arjunsuresh/q2a-top-search) by [Arjun](http://gateoverflow.in/user/Arjun). Adds a widget to display the top searched words. Also have an option to display top seacrhed tags if using [Tag Search](https://github.com/arjunsuresh/tag-search) plugin. - [User Signatures Plugin](https://github.com/NoahY/q2a-signatures) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to define a signature for all of their posts. Also [forked here](https://github.com/pupi1985/q2a-signatures) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). @@ -165,7 +166,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Newsletter-favtags](https://github.com/ProThoughts/q2apro-newsletter-favtags) by [q2apro.com](https://github.com/q2apro).Users can subscribe to their favorite tags and the plugin emails them daily if there are new questions. - [Newsletter Plugin](https://github.com/NoahY/q2a-newsletter) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to receive an HTML newsletter of the latest top questions, answers and comments. - [Tag and Category Notification](https://github.com/amiyasahu/q2a-email-notification) by [Amiya Sahu](http://amiyasahu.com/). When a question is asked with a tag/category, emails users who favorited that tag/category. -- [Votes-manager](https://github.com/ProThoughts/q2apro-votes-manager) by [q2apro.com](https://github.com/q2apro).Lists all votes made in your forum.](https://github.com/q2apro).Sends emails to users about activities on questions they have favorited. +- [Votes-manager](https://github.com/ProThoughts/q2apro-votes-manager) by [q2apro.com](https://github.com/q2apro). Lists all votes made in your forum. Sends emails to users about activities on questions they have favorited. ## Anti-spam tools @@ -216,4 +217,4 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Comment Voting Plugin](https://github.com/NoahY/q2a-comment-voting) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows comments to be voted on, similarly to questions and answers. - [Question Closing](https://github.com/NoahY/q2a-close) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows questions to be closed in Q2A 1.4.x. -Click for documentation on [creating Q2A plugins](/plugins/) (requires PHP programming). +Click for documentation on [creating Q2A plugins]({{ site.baseurl }}/plugins/) (requires PHP programming). diff --git a/addons/themes.md b/addons/themes.md index 3a40a8f..21dac4f 100644 --- a/addons/themes.md +++ b/addons/themes.md @@ -2,6 +2,7 @@ layout: page menu: addons title: "Question2Answer Add-ons - Themes" +slug: addons-themes --- # Question2Answer Themes @@ -11,6 +12,7 @@ To install a theme, place its directory in Q2A's `qa-theme` directory, then open > 🟢 Status: up-to-date | 🔵 Status: Likely up-to-date | 🟡 Status: Hasn't been updated in a while | 🔘 Status: Inactive for a while | 🔴 Status: Unknown. > Note that "*Inactive*" & "*Unknown*" themes doesn't necessarily mean that they do not work. Some themes just don't require to be updated as frequently. +## Free Themes - [MayroPro](https://github.com/MominRaza/MayroPro) (A Google Material Design Theme with Dark Mode, RTL, PWA Support) by [Momin Raza](https://mominraza.github.io). - [Blog Theme](https://github.com/ostack/qa-ostack-blog-theme) by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146) based on SnowFlat. [Demo](https://www.ostack.cn). This theme can show pictures and content in question lists, to make the Q2A site look like a blog. @@ -34,9 +36,12 @@ To install a theme, place its directory in Q2A's `qa-theme` directory, then open - [Twenty Eleven](http://devmx.de/en/themes/twentyeleven-fur-q2a) by [Maximilian Narr](http://devmx.de/). For use with the WordPress theme of the same name. - [TwentyTwelve](https://github.com/q2a-projects/Q2A-TwentyTwelve) by [Towhidn](https://github.com/q2a-projects). Based on WordPress default theme, TwentyTwelve. - [Twitter Bootstrap](https://github.com/harshjv/q2a-bootstrap) by [Harsh J. Vakharia](http://twitter.com/harshjv). For Q2A 1.5.3 only, since it heavily modifies the core to work with [Bootstrap](https://github.com/twitter/bootstrap). + +## Paid Themes + - [Paid] [Polaris theme](https://rxchun.github.io/shop/polaris/) by [Chun](https://www.question2answer.org/qa/user/gold-developer). Polaris is a carefully thought Material Design theme, Ranking all 100's on Google PageSpeed for Performance, Structure, Best Practices & SEO. Mobile App like theme with Dark Mode toggle. RTL ready and supports [plugins](https://www.question2answer.org/qa/102051/premium-theme-polaris-current-v2-14). - [Paid] [AVEN theme](https://rxchun.github.io/shop/aven/) by [Chun](https://www.question2answer.org/qa/user/gold-developer). Shape shifter. Blog type of theme with thumbnail switch. RTL ready. - [Paid] [Frapuchino theme](https://rxchun.github.io/shop/frapuchino/) by [Chun](https://www.question2answer.org/qa/user/gold-developer). Shape shifter. Blog type of theme with thumbnail switch. RTL ready. - [Paid] [Legacy theme](https://rxchun.github.io/shop/legacy/) by [Chun](https://www.question2answer.org/qa/user/gold-developer). Fast, Simple, Material Design theme with inbuilt support for Q2A Blog Tool and other plugins. RTL ready. -You can also [create your own themes](/themes/) using CSS only, or (optionally) [modify the HTML code](/themes/#creating-an-advanced-theme-for-question2answer). +You can also [create your own themes]({{ site.baseurl }}/themes/) using CSS only, or (optionally) [modify the HTML code]({{ site.baseurl }}/themes/#creating-an-advanced-theme-for-question2answer). diff --git a/addons/translations-old.md b/addons/translations-old.md index d7bbe83..2992ebb 100644 --- a/addons/translations-old.md +++ b/addons/translations-old.md @@ -2,6 +2,7 @@ layout: page menu: addons title: "Question2Answer - old language packs" +slug: addons-translate-old --- # Question2Answer old language files diff --git a/addons/translations.md b/addons/translations.md index 6f1b66a..56e6395 100644 --- a/addons/translations.md +++ b/addons/translations.md @@ -2,11 +2,12 @@ layout: page menu: addons title: "Question2Answer - Add-ons - Language files" +slug: addons-translate --- # Question2Answer Language Files -The language files below are marked by the corresponding Question2Answer version. You can safely install a language file from a different version, and [update it with the missing phrases](/translate/). To install a language, place its directory (such as `fr` or `ru`) in Q2A's `qa-lang` directory, then open the General section of the Admin panel and choose the language from the menu provided. +The language files below are marked by the corresponding Question2Answer version. You can safely install a language file from a different version, and [update it with the missing phrases]({{ site.baseurl }}/translate/). To install a language, place its directory (such as `fr` or `ru`) in Q2A's `qa-lang` directory, then open the General section of the Admin panel and choose the language from the menu provided. ## Q2A 1.8 @@ -89,6 +90,6 @@ The language files below are marked by the corresponding Question2Answer version - [Q2A 1.6.x - Ukrainian (Українська)](http://sourceforge.net/projects/q2a-lang-uk/) by Andriy Bokiy. - [Q2A 1.6.x - Vietnamese (Tiếng Việt)](http://annguyenco.com/hoidap/vi-qa-lang.zip) by [AnNguyenCo](http://www.annguyenco.com). Also: [Updated version](https://www.dropbox.com/s/z1gzuow0rz4rkyu/vi.zip?dl=0) by [NTHQ Team](https://namgivu.wordpress.com/2015/01/09/vietnamese-translation-for-question2answer-website/). -_View [older language files](/addons/translations-old/)._ +_View [older language files]({{ site.baseurl }}/addons/translations-old/)._ -If your language is not listed, or is out-of-date, you can [create or update language files](/translate/) using a text editor. +If your language is not listed, or is out-of-date, you can [create or update language files]({{ site.baseurl }}/translate/) using a text editor. diff --git a/code/external.md b/code/external.md index e0b2f5f..5eb7901 100644 --- a/code/external.md +++ b/code/external.md @@ -2,11 +2,12 @@ layout: page menu: code title: "Question2Answer - Developers - External Access" +slug: code-external --- # Accessing Question2Answer from external PHP code -Question2Answer provides a self-contained environment for your Q&A site. However you may wish to access parts of Q2A from other PHP code running on your server, e.g. to access the Q2A database, check if a Q2A user is logged in, or call [other functions](/code/functions/). +Question2Answer provides a self-contained environment for your Q&A site. However you may wish to access parts of Q2A from other PHP code running on your server, e.g. to access the Q2A database, check if a Q2A user is logged in, or call [other functions]({{ site.baseurl }}/code/functions/). To access Q2A 1.3.2 or later from an external script, simply `require_once` the file `qa-include/qa-base.php` from Q2A's directory, along with any other Q2A files containing functions you need. You can then freely call any of the functions defined within these files. Each function is documented at the top of the function definition. (Note: Versions of Q2A prior to 1.3.2 also required you to define `QA_BASE_DIR` and call `qa_base_db_connect()` with a failure handler - see [here](http://www.question2answer.org/qa/4583/login-system-outside-q%26a-directory#a4608).) diff --git a/code/functions.md b/code/functions.md index aa6b6e0..9472566 100644 --- a/code/functions.md +++ b/code/functions.md @@ -2,6 +2,7 @@ layout: page menu: code title: "Question2Answer - Developers - Selected Functions" +slug: code-functions --- # Selected functions in Question2Answer @@ -18,7 +19,7 @@ Many of these functions take optional additional parameters which are not shown ## General functions in qa-base.php -- **`qa_exit($reason)`** calls the `shutdown()` method of any installed [process modules](/plugins/modules-process/), passing the supplied `$reason`. Then it terminates execution of the PHP script with PHP's `exit;` command. Requires Q2A 1.5+. +- **`qa_exit($reason)`** calls the `shutdown()` method of any installed [process modules]({{ site.baseurl }}/plugins/modules-process/), passing the supplied `$reason`. Then it terminates execution of the PHP script with PHP's `exit;` command. Requires Q2A 1.5+. - **`qa_fatal_error($message)`** outputs and logs `$message`, then calls `qa_exit('error');` to terminate execution. - **`qa_list_modules($type)`** returns an array containing the names of all installed modules of type `$type`. - **`qa_load_module($type, $name)`** returns an object belonging to the class defined in module `$name` of type `$type`, or `null` if it cannot be loaded. You can use this to communicate between multiple modules within a plugin, or between different plugins. Note that multiple calls to `qa_load_module()` with the same `$type` and `$name` will return the same object. @@ -36,7 +37,7 @@ Many of these functions take optional additional parameters which are not shown - **`qa_is_https_probably()`** returns whether the current request is using the HTTPS protocol. It is hard to determine this absolutely. - **`qa_is_human_probably()`** returns `true` if the current request appears to be from a person using a web browser, rather than a search engine bot or automated script. This assessment is based on the HTTP user agent containing a known string such as `MSIE` or `Firefox`. Note that it is perfectly possible for a malicious bot or script to masquerade as a web browser. - **`qa_is_mobile_probably()`** returns `true` if the current request appears to be from a mobile phone. This assessment is based on the presence of mobile HTTP headers or the HTTP user agent containing a mobile identifying string. Requires Q2A 1.5+. -- **`qa_lang($identifier)`** and **`qa_lang_html($identifier)`** return a localized language phrase from the appropriate `qa-lang-*.php` file. For example, `qa_lang('main/cancel_button')` would return the phrase with key `'cancel_button'` from the file `qa-lang-main.php`. The phrase returned by `qa_lang_html()` is escaped for output in HTML. From Q2A 1.5+, language files can also be [registered by plugins](/plugins/) and then accessed via these functions. +- **`qa_lang($identifier)`** and **`qa_lang_html($identifier)`** return a localized language phrase from the appropriate `qa-lang-*.php` file. For example, `qa_lang('main/cancel_button')` would return the phrase with key `'cancel_button'` from the file `qa-lang-main.php`. The phrase returned by `qa_lang_html()` is escaped for output in HTML. From Q2A 1.5+, language files can also be [registered by plugins]({{ site.baseurl }}/plugins/) and then accessed via these functions. - **`qa_path_to_root()`** returns the URL to the root of the Q2A site, relative to the currently requested page. Requires Q2A 1.5+. - **`qa_path($request)`** returns the URL for Q2A page `$request`, relative to the currently requested page. You can also call **`qa_path($request, $params)`** to add GET-style parameters to the URL, where `$params` is an array of parameter name `=>` parameter value, not yet urlencoded. **To ensure compatibility with different Q2A URL structures, please use this (or a related `qa_path_*()` function to build URLs instead of hard-coding their structure.** - **`qa_path_html($request)`** and **`qa_path_html($request, $params)`** return the URL for Q2A page `$request` (with optional `$params`), relative to the currently requested page, escaped for output in HTML. Usage example: @@ -46,7 +47,7 @@ Many of these functions take optional additional parameters which are not shown - **`qa_q_path($questionid, $title, $absolute, $showtype, $showid)`** and **`qa_q_path_html(...)`** return the URL for question `$questionid` which has `$title` as its title. If `$absolute` is `true`, this will be an absolute URL, otherwise it will be relative to the currently requested page. To link to a particular answer or comment within that question page, pass `'A'` or `'C'` as the `$showtype` and the `postid` of the answer or comment in `$showid`. Otherwise, pass `null` or omit these last two parameters. The URL returned by `qa_q_path_html()` is escaped for output in HTML. Requires Q2A 1.5+. - **`qa_self_html()`** returns the relative URL for the current Q2A page, preserving URL parameters, escaped for output in HTML. It is particularly useful for creating form tags, e.g. `echo '
    ';` - **`qa_redirect($request)`** and **`qa_redirect($request, $params)`** perform an HTTP redirect to a Q2A page, specified in the same way as the parameters to `qa_path()`. -- **`qa_redirect_raw($url)`** performs an HTTP redirect to `$url`, without processing the URL in any way. Generally you should use `qa_redirect()` however this function can be useful for [login modules](/plugins/modules-login/). +- **`qa_redirect_raw($url)`** performs an HTTP redirect to `$url`, without processing the URL in any way. Generally you should use `qa_redirect()` however this function can be useful for [login modules]({{ site.baseurl }}/plugins/modules-login/). - **`qa_retrieve_url($url)`** returns the content of remote `$url`, using PHP's `file_get_contents()` or curl functions. - **`qa_opt($name)`** gets the value of the option labelled `$name` from Q2A's internal options storage. - **`qa_opt($name, $value)`** sets the option labelled `$name` to `$value` in Q2A's internal options storage. To prevent interference with Q2A or other plugins, your options should be named using a prefix which is unique to your plugin. Do not use this to store large pieces of content, since all options are retrieved for every Q2A page request - instead, look at `app/blobs.php`. @@ -87,7 +88,7 @@ Many of these functions take optional additional parameters which are not shown Note that all `qa_post_...()` functions will send the appropriate email notifications and event reports, as well as update database indexes and counts. This can be prevented with the `qa_suspend...()` functions, described elsewhere on this page. -- **`qa_post_create($type, $parentpostid, $title, $content, $format, $categoryid, $tags, $userid)`** creates a new post in the database, and returns its `postid`. Set `$type` to `'Q'` for a new question, `'A'` for an answer, or `'C'` for a comment. To queue the post for moderation (requires Q2A 1.5+), set `$type` to `'Q_QUEUED'`, `'A_QUEUED'`, or `'C_QUEUED'`. For questions, set `$parentpostid` to the `postid` of the answer to which the question is related, or `null` if (as in most cases) the question is not related to an answer. For answers, set `$parentpostid` to the `postid` of the question being answered. For comments, set `$parentpostid` to the `postid` of the question or answer to which the comment relates. The `$content` and `$format` parameters go together - if `$format` is `''` then `$content` should be in plain UTF-8 text, and if `$format` is `'html'` then `$content` should be in UTF-8 HTML. Other values of `$format` may be allowed if an appropriate [viewer module](/plugins/modules-viewer/) is installed. The `$title`, `$categoryid` and `$tags` parameters are only relevant when creating a question - `$tags` can either be an array of tags, or a string of tags separated by commas. The new post will be assigned to `$userid` if it is not `null`, otherwise it will be anonymous. Additional optional parameters set notification, extra question fields and names on anonymous posts. +- **`qa_post_create($type, $parentpostid, $title, $content, $format, $categoryid, $tags, $userid)`** creates a new post in the database, and returns its `postid`. Set `$type` to `'Q'` for a new question, `'A'` for an answer, or `'C'` for a comment. To queue the post for moderation (requires Q2A 1.5+), set `$type` to `'Q_QUEUED'`, `'A_QUEUED'`, or `'C_QUEUED'`. For questions, set `$parentpostid` to the `postid` of the answer to which the question is related, or `null` if (as in most cases) the question is not related to an answer. For answers, set `$parentpostid` to the `postid` of the question being answered. For comments, set `$parentpostid` to the `postid` of the question or answer to which the comment relates. The `$content` and `$format` parameters go together - if `$format` is `''` then `$content` should be in plain UTF-8 text, and if `$format` is `'html'` then `$content` should be in UTF-8 HTML. Other values of `$format` may be allowed if an appropriate [viewer module]({{ site.baseurl }}/plugins/modules-viewer/) is installed. The `$title`, `$categoryid` and `$tags` parameters are only relevant when creating a question - `$tags` can either be an array of tags, or a string of tags separated by commas. The new post will be assigned to `$userid` if it is not `null`, otherwise it will be anonymous. Additional optional parameters set notification, extra question fields and names on anonymous posts. - **`qa_post_set_content($postid, $title, $content, $format, $tags)`** changes the data stored for post `$postid` based on any of the `$title`, `$content`, `$format` and `$tags` parameters passed which are not `null`. The meaning of these parameters is the same as for `qa_post_create()` above. Additional optional parameters allow changes to post notification, extra question fields, names on anonymous posts and can identify the user who made the change. - **`qa_post_set_category($postid, $categoryid)`** changes the category of `$postid` to `$categoryid`. The category of all related posts (shown together on the same question page) will also be changed. - **`qa_post_set_selchildid($questionid, $answerid)`** sets the selected best answer of `$questionid` to `$answerid` (or to none if `$answerid` is `null`). diff --git a/code/structure.md b/code/structure.md index 8f03ff4..c690ba7 100644 --- a/code/structure.md +++ b/code/structure.md @@ -2,6 +2,7 @@ layout: page menu: code title: "Question2Answer - Developers - Q2A code structure" +slug: code-structure --- # Question2Answer application structure @@ -23,7 +24,7 @@ All HTTP requests for Q2A are routed through the root `index.php` file. This fil For the latter four types above, we include `qa-include/qa-base.php` which initializes the PHP environment, various constants and settings, and then connects to the database. It also defines an autoloader (from v1.7+) which automatically loads core classes of a specific format: -1. From Q2A 1.9 namespaces are used. The main core namespace is `Q2A` which maps to the `qa-src/` directory. So a class of the form `\Q2A\Storage\FileCacheDriver` will automatically load the file `qa-src/Storage/FileCacheDriver.php`. See the [coding style](/contribute/coding-style/#new-autoloaded-classes) page for full details. +1. From Q2A 1.9 namespaces are used. The main core namespace is `Q2A` which maps to the `qa-src/` directory. So a class of the form `\Q2A\Storage\FileCacheDriver` will automatically load the file `qa-src/Storage/FileCacheDriver.php`. See the [coding style]({{ site.baseurl }}/contribute/coding-style/#new-autoloaded-classes) page for full details. 2. In Q2A 1.7-1.8, classes of the form `Q2A_Util_Usage` map to files in the `qa-include/Q2A/` directory, e.g. `qa-include/Q2A/Util/Usage.php`. These are being phased out in favor of namespaces. Here we "normalize" the request, which converts the URL from one of multiple formats (parameters or SEO-friendly) into a standard format such as `/activity` or `/question/1234/title`. diff --git a/contribute/coding-style.md b/contribute/coding-style.md index 2549366..a73d691 100644 --- a/contribute/coding-style.md +++ b/contribute/coding-style.md @@ -2,6 +2,7 @@ layout: page menu: contribute title: "Question2Answer Coding Style guidelines" +slug: contribute-coding-style --- # Question2Answer Coding Style guidelines diff --git a/contribute/docs.md b/contribute/docs.md index 61811fd..1f94c15 100644 --- a/contribute/docs.md +++ b/contribute/docs.md @@ -2,6 +2,7 @@ layout: page menu: contribute title: "Question2Answer Jekyll docs" +slug: contribute-docs --- # Contributing to the Q2A documentation diff --git a/contribute/index.md b/contribute/index.md index ddb9d85..9cf378b 100644 --- a/contribute/index.md +++ b/contribute/index.md @@ -2,6 +2,7 @@ layout: page menu: contribute title: "Contribute" +slug: contribute --- # Contributing to Question2Answer diff --git a/contribute/unit-tests.md b/contribute/unit-tests.md index 085b620..70cbdfd 100644 --- a/contribute/unit-tests.md +++ b/contribute/unit-tests.md @@ -2,6 +2,7 @@ layout: page menu: contribute title: "Question2Answer unit test guidelines" +slug: contribute-unit-tests --- # Writing unit tests for Question2Answer diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..b2872df --- /dev/null +++ b/css/styles.css @@ -0,0 +1,1189 @@ +/* ================== + Base styles +================== */ + +/* Colors: +Orange = #ff773d +Dark orange = #ff6b2b +*/ + +* { + box-sizing: border-box; +} + +html { + scroll-padding-top: 90px; +} + +body { + margin: 0; + padding: 0; + font: 400 16px/24px Roboto,sans-serif; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + color: #424242; + /* background-color: #f8f8f8; */ + background-color: #ffffff; + text-align: left; +} + +a, a:visited { + text-decoration: none; + color: #ff6b2b; +} + +a:hover, a:active { + color: #ff6b2b; +} + +.page-container a:hover, +.footer a:hover { + text-decoration: underline; +} + + +h1, h2, h3 { + font-weight: 500; +} + +/* +h1, h2, h3, h4, h5, h6 { + color: #424242; +} + +h1 { + font-size: 2rem; + line-height: 2.5rem; + margin: 0 0 .5rem; +} + +h2 { + font-size: 1.5rem; + line-height: 2rem; + margin: 2rem 0 .5rem; +} + +h3 { + font-size: 1.25rem; + font-weight: 500; + line-height: 1.75rem; + margin: 1.5rem 0 .5rem; +} + +h4 { + font-size: 1rem; + font-weight: 500; + line-height: 1.5rem; + margin: 1.25rem 0 .25rem; +} + +h5 { + font-size: .875rem; + font-weight: inherit; + line-height: 1.5rem; + margin: 1rem 0 .25rem; +} +*/ + +ul, ol { + padding-left: 1.8rem; + margin-left: 0; + margin-bottom: 2em; +} + +img { + max-width: 100%; + height: auto; +} + +code, pre { + background-color: #eeeeee; + font-family: "Menlo", "Consolas", "Monaco", "Ubuntu Mono", "Andale Mono", "Bitstream Vera Sans Mono", monospace; + font-size: 13px; +} + +/* inline code */ +code { + word-wrap: break-word; /* old syntax (IE/Edge) */ + overflow-wrap: anywhere; + word-break: break-word; + padding: 2px 4px; + border-radius: 3px; +} + +/* block code */ +pre { + display: grid; + width: 100%; + background-color: #f6f6f6; + overflow: hidden; + border-radius: 6px; + font-size: .8125rem; + font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "Liberation Mono", Menlo, Monaco, Consolas, monospace; + line-height: 1.30769231; +} +pre code { + background-color: #f6f6f6; + padding: 0; + max-height: 600px; + color: inherit; + font-family: inherit; + tab-size: 4; + font-size: 13px; + overflow: auto; + padding: 12px; +} + +blockquote { + background-color: #FFE0B2; + padding: .1rem 1rem; + margin: 0; +} + +/* =================== + PREDEFINED CLASSES +======================= */ + +.no-select, .noSelect, .noselect { + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.flex-row { + display: flex; + flex-direction: row; +} +.flex-item { + display: flex; + justify-content: flex-start; + align-items: center; + flex-wrap: nowrap; +} +.not-flex-middle { + width: 25%; + padding: 0 2rem; +} + +.display-none, .displayNone { + display: none; +} + +.docs-svg { + pointer-events: none; +} + +.docs-svg path { + fill: inherit; +} + +.docs-caret { + position: relative; + display: inline-block; + width: 18px; + height: 18px; + vertical-align: middle; + pointer-events: none; +} +.docs-caret:before { + content: ""; + display: inline-block; + width: 20%; + height: 20%; + border-right: 2px solid; + border-bottom: 2px solid; + position: absolute; + top: 30%; + left: 50%; + transform: translateX(-50%) translateY(-30%) rotate(45deg); +} + +.nav-item:focus .docs-caret:before { + width: 40%; + border-right: none; + transform: translateX(-50%) translateY(-30%) rotate(0deg); +} + +.darkpane, .darkPane, .dark-pane { + position: fixed; + background-color: #000; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1000; + opacity: .5; + z-index: 1; + opacity: 0; + visibility: hidden; + pointer-events: none; + transition: opacity .25s ease; +} +.darkpane.active, .darkPane.active, .dark-pane.active { + opacity: .5; + visibility: visible; + pointer-events: auto; + transition: opacity .25s ease; +} + +.clear, .clearFloat, .clear-float { + clear: both; +} + + +/* ================== + LAYOUT +================== */ + +body.page { + margin-top: 66px; +} + +.container { + max-width: 1200px; +} + +.wave-container { + position: absolute; + top: 0; + left: 0; + z-index: -1; + width: 100%; + height: 170px; + background: transparent url(../images/wave-combo.svg) 20% top / cover no-repeat; +} + +.wave-container .docs-svg path { + fill: revert-layer; +} + +.header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 999999; + width: 100%; + height: 66px; + padding: 0 2rem; + transition: box-shadow .218s, background-color .218s; +} +.header.sticky { + /* background-color: #ff773d; */ + background-color: #ffffff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + color: #3c4043; + fill: #3c4043; +} + +body:not(.template-homepage) .header { + background-color: #ffffff; + border-bottom: 1px solid rgb(0 0 0 / 10%); +} + +.template-homepage .header:not(.sticky) { + color: #ffffff; + fill: #ffffff; +} + +/* Necessary to cap the width, without afecting background fill color */ +.header-container { + height: 66px; + padding: 0 2rem; + max-width: 1500px; + margin: 0 auto; +} + + +#main-nav-trigger { + margin-right: 1rem; + cursor: pointer; +} + +.logo-container { + padding-right: 2rem; +} +.header-logo { + display: inline-flex; + max-width: 225px; +} + +.header.sticky .header-logo .docs-svg path, +body:not(.template-homepage) .header .header-logo .docs-svg path { + fill: revert-layer; +} + +.header-logo .docs-svg, .logo-img { + width: 100%; + max-width: 225px; + height: auto; +} + +body:not(.template-homepage) .header .white-logo { + display: none; +} + +.template-homepage .normal-logo, .template-homepage .header.sticky .white-logo { + display: none; +} +.template-homepage .header.sticky .normal-logo { + display: initial; +} + +.nav-container { + flex-grow: 1; +} + +.nav-container .header-logo { + padding: 1rem; + max-width: 175px; +} + +.nav { + margin: 0; + padding: 0; +} + +.nav li { + display: inline-block; + position: relative; +} +.nav li ul { + color: initial; + fill: initial; +} + +.mega-menu { + width: 100%; +} + +.mega-menu .nav-main .sub-nav-item { + border-radius: 6px; +} + +a.nav-item, .nav-item { + padding: .5rem 1rem; + font-size: .938rem; + font-weight: 500; + line-height: 14px; + color: inherit; + white-space: nowrap; + border-radius: 30px; + cursor: pointer; +} +/* Fix for carrot, dropdown menu */ +.nav-main .nav-item { + padding-right: .5rem; +} +.toggleChildren a, .toggleChildren span { + cursor: pointer; +} + +.nav-item .docs-svg { + height: 22px; + width: 22px; + vertical-align: top; + fill: inherit; +} + +.nav-item:hover .docs-svg { + fill: inherit; +} + +.selected-nav, a.selected-nav, +.nav-item:hover, a.nav-item:hover, +.sub-nav-item:hover, a.sub-nav-item:hover, +.current-dropdown, +.nav-item.current-dropdown { + box-shadow: none; + background-color: #f1f3f4; + color: #3c4043; + fill: #3c4043; +} + +ul.sub-nav { + position: absolute; + top: -6px; + z-index: 1; + margin: 0; + padding: 12px 0; + background-color: #ffffff; + min-width: 100%; + width: max-content; + border-radius: 6px; + box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.15); +} + +/* top:-6px; for filters, but top:45px; for a tooltip style for the .header */ +.header ul.sub-nav { + top: 40px; +} +.mega-menu .nav-main:before, +.header .nav-main-second ul.sub-nav:before { + content: ''; + position: absolute; + left: 30px; + bottom: 100%; + border-bottom: 8px solid #ffffff; + border-left: 8px solid transparent; + border-right: 8px solid transparent; +} +.sub-nav li { + display: block; +} +.sub-nav .sub-nav-item, .sub-nav a.sub-nav-item { + color: #424242; + display: block; + padding: .3rem 1rem; +} + +/* =============== + BREADCRUMBS +=============== */ + +.breadcrumbs { + display: block; + font-size: .875rem; + line-height: 3.5rem; + margin: 0; + padding: 0 1rem; + white-space: nowrap; +} + +.breadcrumb-item { + display: inline-block; +} + +.breadcrumb-item:after { + content: "\e315"; + font-family: 'Material Icons'; + font-style: normal; + vertical-align: middle; + color: inherit; + height: 1.5rem; + width: 1.5rem; + font-size: 150%; + margin: 0 .5rem; + font-weight: initial; +} + +.breadcrumb-item:last-child.breadcrumb-item:after { + display: none; +} + + +/* =============== + MAIN SECTION +================ */ + +.notice-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 999999; +} + +.notice { + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + z-index: 1; + background-color: #ffffff; + width: 70%; + max-width: 90vw; + height: 80vh; + overflow: hidden; + border-radius: 12px; +} + +.notice-text, .notice-image { + width: 50%; + height: 100%; + float: right; +} + +.notice-text { + padding: 2rem; + overflow-Y: auto; + max-height: inherit; +} + +.notice-text > :first-child { + padding-top: 0; + margin-top: 0; +} + +.notice-image { + background-image: url(../images/pexels-leeloothefirst-5428824-800.webp); + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +.close-notice { + position: absolute; + top: 0; + right: 0; + padding: 1rem; + cursor: pointer; + border-radius: 50px; + transition: background-color .25s ease; +} + +.close-notice:hover { + background-color: rgb(0 0 0 / 10%); +} + +.close-sheet { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 0; + background-color: rgb(0 0 0 / 50%); +} + +.page-status { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + font-size: 13px; + display: flex; + flex-direction: row; + align-items: center; + background-color: rgb(32, 33, 36); + color: #ffffff; + fill: #ffffff; + padding: .6rem 2rem; + border-radius: 6px; + width: max-content; + min-width: 24vw; + max-width: 95%; +} + +.close-page-status { + cursor: pointer; + padding: 4px; + border-radius: 30px; +} + +.close-page-status:hover { + background-color: rgb(255 255 255 / 10%); +} + +.template-addons-themes .page-content blockquote, +.addons-themes .page-content blockquote, +.template-addons-plugins .page-content blockquote, +.addons-plugins .page-content blockquote { + font-size: small; +} + +.page-container { + margin: 0 auto; + max-width: 1200px; +} + +.template-homepage .home-intro-container { + padding: 9rem 2rem 2rem 2rem; +} + +.docs-container { + display: flex; + margin: 0 auto; + max-width: 75.125rem; + padding: 0 1rem; + width: 100%; +} + +.page-content { + align-self: flex-start; + flex: 1; + max-width: 52rem; + padding: 0.8rem 1rem; + border: 1px solid #dadce0; + border-radius: 0.5rem; +} + +#question2answer-plugins ~ ul li, +#question2answer-themes ~ ul li { + border-bottom: 1px solid #e0e0e0; + margin: .3rem 0; +} + +.rep-date { + display: inline-block; + background-color: #e0e0e0; + padding: 0px 6px; + border-radius: 3px; + margin-inline-end: 6px; + margin-bottom: 6px; + cursor: help; +} + +.rep-date-0, .rep-date-1 { + background-color: #18ab60; /* Green */ + color: #ffffff; +} +.rep-date-2 { + background-color: #3498db; /* Blue */ + color: #ffffff; +} +.rep-date-3 { + background-color: #ffeb3b; /* Yellow */ +} +.rep-date-4, .rep-date-5 { + background-color: #e0e0e0; /* Grey */ +} +.rep-date-bad { + background-color: #e74c3c; /* Red */ + color: #ffffff; +} + +.rep-date + .rep-date { + display: none; +} + +.page-sidebar { + width: 21.125rem; + font-size: 15px; + position: sticky; + top: 66px; + margin: 0; + list-style: none; + max-height: calc(100vh - 66px); + overflow: auto; + height: max-content; /* Fix for position:sticky stopped being sticky short */ +} + +.docs-nav-title { + position: relative; + margin-top: 0; + padding-left: 2rem; +} +.docs-nav-title:before { + position: absolute; + top: 0; + left: 0; + content: "article"; + height: 1.5rem; + width: 1.5rem; + font: normal 1.5rem 'Material Icons'; + color: #ff773d; +} + +.docs-nav { + padding-left: 1rem; + margin: 0; + list-style: none; +} + +.docs-nav-item { + padding: .3rem 0 .3rem 3rem; + position: relative; + cursor: pointer; +} + +.docs-nav-item:before { + content: "article"; + font-family: 'Material Icons'; + width: 1.5rem; + height: 1.5rem; + line-height: 1.5rem; + font-size: 150%; + font-weight: initial; + position: absolute; + top: 5px; + left: 0; + color: #ff6b2b; +} + +.docs-nav-item.active { + font-weight: bold; +} + +.jump-top-container { + display: block; + margin: 0 40px; + padding-bottom: 6rem; + position: relative; + text-align: right; +} +.jump-top { + display: flex; + position: absolute; + right: 0; + overflow: hidden; + background-color: #ffffff; + height: 56px; + width: 56px; + font-size: 24px; + align-items: center; + justify-content: center; + border-radius: 50%; + box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3), 0 1px 3px 1px rgba(60, 64, 67, .15); + transition: background .2s, box-shadow .2s, color .2s; + cursor: pointer; +} +.jump-top.active { + position: fixed; + right: 40px; + bottom: 40px; + box-shadow: 0 2px 3px 0 rgba(60, 64, 67, .3), 0 6px 10px 4px rgba(60, 64, 67, .15); +} + +.jump-top .material-icons { + pointer-events: none; +} + +.footer { + border-top: 1px solid rgb(0 0 0 / 10%); + padding: 0 2rem; +} +.footer-container { + max-width: 1500px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 2rem; + padding: 3rem 2rem; + margin: 0 auto; +} +.footer-nav-link { + margin: 0 1rem; +} + +.footer-nav ul { + display: flex; + flex-wrap: wrap; + justify-content: inherit; + margin: 0; + padding: 0; + list-style: none; +} + +.footer-nav ul li { + display: inline; +} + + + +/* ================== + HOMEPAGE +================== */ + +.home-intro { + max-width: 800px; + margin: 0 auto; + text-align: center; + font-size: 1.5rem; + font-weight: normal; +} + +.showcase-cols { + display: grid; + grid-template-columns: 1fr; + grid-gap: 2rem; + margin: 2rem auto; + max-width: 1000px; +} + +.card-showcase { + display: flex; + align-items: flex-start; + flex-direction: row; + flex: 1 0 100%; + padding: 1.5rem; + border: 1px solid #dadce0; + border-radius: 0.5rem; +} + +.card-showcase-icon { + display: flex; + position: relative; + overflow: hidden; + flex: 0 0 50px; + align-items: center; + justify-content: center; + border-radius: 52px; + width: 50px; + height: 50px; + background-color: #ff773d; + color: #ffffff; + font-size: 2rem; + margin-right: 1rem; +} +.card-showcase-icon:after { + content: ""; + position: absolute; + left: 50%; + width: 50%; + height: 100%; + background-color: #000000; + opacity: 0.04; +} +.card-showcase-icon .material-icons { + font-size: inherit; +} + +.card-showcase-info h4 { + margin: 0; +} +.card-showcase-info p, .card-showcase-info ul { + color: #707070; +} +.card-showcase-info ul { + padding-left: 1.25rem; +} + +.text-small { + font-size: 0.85rem; +} + + +/* =============== + DOCS PAGES +=============== */ + +a[href*="/releases/question2answer"][href$=".zip"] { + margin-left: 1rem; + padding: 0.5rem; + vertical-align: bottom; + border-radius: 8px; + background: #ff773d; + color: #fff; + font-size: 0.75rem; + text-transform: uppercase; +} +a[href*="/releases/question2answer"][href$=".zip"]:active, +a[href*="/releases/question2answer"][href$=".zip"]:hover { + background: #ff6b2b; + text-decoration: none; +} + + +/* =============== + MEDIA QUERIES +================= */ + +@media (min-width: 1500px) { + .wave-container { + height: 200px; + background-size: 100%; + } +} + +@media (min-width: 1024px) { + .mobile-only { + display: none; + } + .nav-main { + position: absolute; + top: 60px; + z-index: 1; + background-color: #ffffff; + padding: 1rem 0; + align-items: unset; + color: #000; + fill: #000; + border-radius: 6px; + box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.15); + } + .nav-main .sub-nav { + display: block; + position: initial; + box-shadow: none; + /* + border-top: 1px solid rgb(0 0 0 / 10%); + border-radius: 0; + margin-top: .5rem; + */ + } + .mega-menu .nav-main > li { + margin: 0 0.5rem; + } + .mega-menu .nav-main > li:not(:last-child):after { + content: ""; + width: 1px; + height: 100%; + background-color: rgb(0 0 0 / 10%); + position: absolute; + top: 0; + right: -.5rem; + } + + .mega-menu .nav-main .toggleChildren { + pointer-events: none; + background-color: unset; + } + .mega-menu .nav-main .toggleChildren .docs-caret, + .mega-menu .nav-main .toggleChildren .docs-svg { + display: none; + } + + .footer-nav-link:first-child { + margin-left: 0; + } + +} + +@media (min-width: 800px) { + .page-sidebar-toggle { + display: none; + } + + .page-content { + padding: 3rem; + } + .page-content .sectionTitle:first-child { + margin-top: 0; + } + .page-sidebar { + padding: 2rem 1rem; + } + + .showcase-cols { + grid-template-columns: 1fr 1fr; + } + +} + + +/* ======================== + MEDIA QUERIES MAX-WIDTH +=========================== */ + +@media (max-width: 1120px) { + .footer-container { + justify-content: center; + } +} + +@media (max-width: 1024px) { + .desktop-only { + display: none; + } + .header .nav-container { + color: initial; + fill: initial; + } + .header-container, .footer { + padding: 0; + } + .nav-container { + display: block; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 2; + width: 265px; + max-width: 80%; + background-color: #ffffff; + transform: translateX(-100%); + transition: transform .25s cubic-bezier(.4,0,.2,1); + } + .nav-container.active { + transform: translateX(0%); + } + .nav-container .nav, .mega-menu { + display: block; + } + .nav-container li { + display: block; + } + .nav-main { + border-bottom: 1px solid rgb(0 0 0 / 10%); + padding-bottom: .5rem; + margin-bottom: .5rem; + } + .mega-menu-trigger { + display: none; + } + a.nav-item, .nav-item { + display: block; + width: 100%; + line-height: 24px; + border-radius: 0; + } + .nav-item .docs-svg { + vertical-align: text-bottom; + } + + .jump-top-container { + padding-bottom: 2rem; + } +} + +@media (max-width: 575px) { + body { + font-size: .85rem; + line-height: 1.4rem; + } + h1, h2, h3, h4, h5, h6 { + line-height: initial; + } + + .header { + padding: 0 1rem; + } + .header-logo { + max-width: 130px; + } + .nav-item:hover .docs-caret:before { + width: 40%; + border-right: none; + transform: translateX(-50%) translateY(-30%) rotate(0deg); + } + .breadcrumbs { + padding: 1rem; + line-height: 1.5rem; + white-space: initial; + border-bottom: 1px solid rgb(0 0 0 / 10%); + } + .breadcrumb-item:after { + height: .5rem; + width: .5rem; + margin: 0; + } + + .template-homepage .home-intro-container { + padding: 7rem 3rem 2rem 3rem; + } + .home-intro { + font-size: .938rem; + } + + .showcase-cols { + grid-gap: 1rem; + margin: 2rem 1rem; + } + .card-showcase { + padding: 1rem .5rem; + } + .card-showcase-icon { + flex: 0 0 35px; + width: 35px; + height: 35px; + font-size: 1rem; + margin-right: .5rem; + } + .card-showcase-info p { + margin-top: .5rem; + } + + .page-content { + border: none; + } + + .notice { + width: 80%; + } + .notice-text, .notice-image { + width: 100%; + float: none; + } + .notice-text { + padding: 1rem; + max-height: calc(80vh - 80px); + } + .notice-image { + height: 100px; + } + +} + +@media (max-width: 545px) { + .footer-nav ul li { + width: 50%; + margin: .5rem auto; + text-align: center; + } +} + +@media (max-width: 475px) { + .page-content { + padding: 0; + } + #question2answer-plugins ~ ul, #question2answer-themes ~ ul { + padding-left: 1rem; + } + .page-status { + padding: .6rem 1rem; + } +} + + +/* ==================== + Syntax highlighting +======================= */ + +.highlight .hll { background-color: #ffffcc } +.highlight .c { color: #808080 } /* Comment */ +.highlight .err { color: #f00000; background-color: #f0a0a0 } /* Error */ +.highlight .k { color: #008000 } /* Keyword */ +.highlight .o { color: #303030 } /* Operator */ +.highlight .cm { color: #808080 } /* Comment.Multiline */ +.highlight .cp { color: #507090 } /* Comment.Preproc */ +.highlight .c1 { color: #808080 } /* Comment.Single */ +.highlight .cs { color: #cc0000 } /* Comment.Special */ +.highlight .gd { color: #a00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #ff0000 } /* Generic.Error */ +.highlight .gh { color: #000080 } /* Generic.Heading */ +.highlight .gi { color: #00a000 } /* Generic.Inserted */ +.highlight .go { color: #808080 } /* Generic.Output */ +.highlight .gp { color: #c65d09 } /* Generic.Prompt */ +/* .highlight .gs { } */ /* Generic.Strong */ +.highlight .gu { color: #800080 } /* Generic.Subheading */ +.highlight .gt { color: #0040d0 } /* Generic.Traceback */ +.highlight .kc { color: #008000 } /* Keyword.Constant */ +.highlight .kd { color: #008000 } /* Keyword.Declaration */ +.highlight .kn { color: #008000 } /* Keyword.Namespace */ +.highlight .kp { color: #003080 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000 } /* Keyword.Reserved */ +.highlight .kt { color: #303090 } /* Keyword.Type */ +.highlight .m { color: #6000e0 } /* Literal.Number */ +.highlight .s { color: #d04020 } /* Literal.String */ +.highlight .na { color: #0060b0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #b00060 } /* Name.Class */ +.highlight .no { color: #003060 } /* Name.Constant */ +.highlight .nd { color: #505050 } /* Name.Decorator */ +.highlight .ni { color: #800000 } /* Name.Entity */ +.highlight .ne { color: #f00000 } /* Name.Exception */ +.highlight .nf { color: #0060b0 } /* Name.Function */ +.highlight .nl { color: #907000 } /* Name.Label */ +.highlight .nn { color: #0e84b5 } /* Name.Namespace */ +.highlight .nt { color: #007000 } /* Name.Tag */ +.highlight .nv { color: #906030 } /* Name.Variable */ +.highlight .ow { color: #000000 } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #6000e0 } /* Literal.Number.Float */ +.highlight .mh { color: #005080 } /* Literal.Number.Hex */ +.highlight .mi { color: #0000d0 } /* Literal.Number.Integer */ +.highlight .mo { color: #4000e0 } /* Literal.Number.Oct */ +.highlight .sb { color: #d04020 } /* Literal.String.Backtick */ +.highlight .sc { color: #0040d0 } /* Literal.String.Char */ +.highlight .sd { color: #d04020 } /* Literal.String.Doc */ +.highlight .s2 { color: #d04020 } /* Literal.String.Double */ +.highlight .se { color: #606060; } /* Literal.String.Escape */ +.highlight .sh { color: #d04020 } /* Literal.String.Heredoc */ +.highlight .si { color: #d04020 } /* Literal.String.Interpol */ +.highlight .sx { color: #d02000; color: #d04020 } /* Literal.String.Other */ +.highlight .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ +.highlight .s1 { color: #d04020 } /* Literal.String.Single */ +.highlight .ss { color: #a06000 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #306090 } /* Name.Variable.Class */ +.highlight .vg { color: #d07000 } /* Name.Variable.Global */ +.highlight .vi { color: #3030b0 } /* Name.Variable.Instance */ +.highlight .il { color: #0000d0 } /* Literal.Number.Integer.Long */ diff --git a/css/styles2020.css b/css/styles2020.css deleted file mode 100644 index 4096da3..0000000 --- a/css/styles2020.css +++ /dev/null @@ -1,546 +0,0 @@ -/* ================== - Base styles -================== */ - -/* Colors: - Orange = #ff773d - Dark orange = #ff6b2b -*/ - -* { - box-sizing: border-box; -} - -body { - margin: 0; - padding: 0; - font: 16px/1.5 "Roboto", "Helvetica", "Arial", sans-serif; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - color: #424242; - background-color: #f8f8f8; - text-align: left; -} - -a, a:visited { - text-decoration: none; - color: #ff6b2b; -} - -a:hover, a:active { - color: #ff6b2b; - text-decoration: underline; -} - -h1 { - margin: 0 0 .5rem; - font-size: 1.6rem; - line-height: 2.5rem; -} - -h2 { - margin: 2rem 0 .5rem; - font-size: 1.4rem; - line-height: 2rem; -} - -h3 { - margin: 1.5rem 0 .5rem; - font-size: 1.2rem; - line-height: 1.75rem; -} - -h4 { - margin: 1.25rem 0 .25rem; - font-size: 1rem; - line-height: 1.5rem; -} - -ul, ol { - margin-left: 0; - padding-left: 1.8rem; -} - -img { - max-width: 100%; - height: auto; -} - -code, pre { - background-color: #f8f8f4; - font-family: "Menlo", "Consolas", "Monaco", "Ubuntu Mono", "Andale Mono", "Bitstream Vera Sans Mono", monospace; - font-size: 13px; -} - -/* inline code */ -code { - word-wrap: break-word; /* old syntax (IE/Edge) */ - overflow-wrap: anywhere; - word-break: break-word; - padding: 1px 3px; -} - -/* block code */ -pre { - padding: 1rem; - line-height: 1.3; - white-space: pre-wrap; -} -pre code { - padding: 0; - color: inherit; - font-family: inherit; - tab-size: 4; - font-size: 13px; -} - -blockquote { - background-color: #FFE0B2; - padding: .1rem 1rem; - margin: 0; -} - - - -/* ================== - Layout -================== */ - -.container { - max-width: 1200px; -} - -.wave-container { - position: absolute; - top: 0; - left: 0; - z-index: 20; - width: 100%; - height: 170px; - background: transparent url("../images/wave-combo.svg") 20% top/cover no-repeat; -} - -@media (min-width: 1500px) { - .wave-container { - height: 200px; - background-size: 100%; - } -} - -.header { - position: absolute; - top: 0; - z-index: 25; - display: flex; - flex-wrap: wrap; - justify-content: center; - width: 100%; -} -.header.sticky { - position: fixed; - background-color: #ff773d; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} -.header-logo { - margin: 1rem; -} - -.logo-img { - display: block; -} - -.top-nav { - display: flex; - flex-grow: 1; - align-items: center; - overflow: auto; -} -a.main-nav-link { - padding: 0.5rem 0.8rem; - border-radius: 0.25rem; - color: #fff; - font-weight: bold; - white-space: nowrap; - transition: background-color 0.2s ease-in-out; -} -a.main-nav-link:hover { - background-color: rgb(0, 0, 0, 0.05); - text-decoration: none; -} -a.main-nav-link-separate { - margin-left: auto; -} - -.page-container { - margin: 9rem auto 0; - padding: 0 0.25rem; - max-width: 1280px; -} -.docs-container { - display: grid; - grid-template-columns: 6fr; - padding: 0 0.5rem; -} - -.page-content { - padding: 0.8rem 1rem; - border: 1px solid #dadce0; - border-radius: 0.5rem; - background-color: #fff; - box-shadow: 2px 2px 4px rgba(80, 80, 80, 0.1); -} - -#question2answer-plugins ~ ul li, -#question2answer-themes ~ ul li { - border-bottom: 1px solid #e0e0e0; - margin: .3rem 0; -} - -.rep-date { - display: inline-block; - background-color: #e0e0e0; - padding: 0px 6px; - border-radius: 3px; - margin-inline-end: 3px; - margin-bottom: 6px; -} - -.rep-date-0, .rep-date-1 { - background-color: #18ab60; /* Green */ - color: #ffffff; -} -.rep-date-2 { - background-color: #3498db; /* Blue */ - color: #ffffff; -} -.rep-date-3 { - background-color: #ffeb3b; /* Yellow */ -} -.rep-date-4, .rep-date-5 { - background-color: #e0e0e0; /* Grey */ -} -.rep-date-bad { - background-color: #e74c3c; /* Red */ - color: #ffffff; -} - -.page-sidebar { - padding: 1.5rem 1rem; -} - -.page-sidebar-toggle { - display: inline-block; - cursor: pointer; - border: 1px solid #dadce0; - padding: 0.5rem; - border-radius: 0.25rem; - background-color: #fff; - color: #ff773d; -} -.page-sidebar-toggle:before { - content: "\e164"; - font-family: 'Material Icons'; - font-style: normal; - font-weight: initial; - color: inherit; - margin-right: 6px; -} -.page-sidebar-content { - display: none; - column-count: 2; -} -.page-sidebar-content.expanded { - display: block; -} - -.docs-nav-title { - position: relative; - margin-top: 0; - padding-left: 2rem; -} -.docs-nav-title:before { - position: absolute; - top: 0; - left: 0; - content: "article"; - height: 1.5rem; - width: 1.5rem; - font: normal 1.5rem 'Material Icons'; - color: #ff773d; -} - -.docs-nav { - list-style: none; - margin: 0 0 1rem; - padding-left: 2rem; -} - -.footer { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 2rem; - margin: 3rem auto 0; - padding: 3rem 1rem; - border-top: 1px solid #dadce0; -} -.footer-nav { - display: flex; - flex-wrap: wrap; - margin-right: 1rem; -} -.footer-nav-link { - margin: 0 1rem; -} -.credit { - margin-left: 1rem; -} - -@media (min-width: 540px) { - h1 { - font-size: 2rem; - } - h2 { - font-size: 1.5rem; - } - h3 { - font-size: 1.25rem; - } - h4 { - font-size: 1rem; - } - ul, ol { - padding-left: 2.2rem; - } - - .page-container { - padding: 0 1rem; - } - .page-content { - padding: 1.6rem 2rem; - } - - .page-sidebar-content { - column-count: 3; - } -} - -@media (min-width: 800px) { - .header { - justify-content: flex-start; - } - .header-logo { - margin-right: 2rem; - } - - .secondary-nav { - margin-left: auto; - } - - .page-container { - margin-top: 12rem; - } - - .page-sidebar-toggle { - display: none; - } - .page-sidebar-content { - display: block; - column-count: 1; - } - - .docs-container { - grid-template-columns: 1fr 5fr; - grid-gap: 1rem; - } - - .page-content { - padding: 1.6rem 2rem; - } - .page-sidebar { - padding: 1.5rem 1rem; - } -} - -@media (min-width: 1220px) { - a.main-nav-link { - font-size: 1.125rem; - } -} - - -/* ================== - Homepage -================== */ - -.home-intro { - max-width: 800px; - margin: 0 auto 2rem; - text-align: center; - font-size: 1.4rem; - font-weight: normal; -} - -.showcase-cols { - display: grid; - grid-template-columns: 1fr; - grid-gap: 1rem; - margin: 2rem auto; - max-width: 1080px; -} - -.card-showcase { - display: flex; - align-items: flex-start; - flex-direction: row; - flex: 1 0 100%; - padding: 0.8rem 1rem; - border: 1px solid #dadce0; - border-radius: 0.5rem; - background-color: #fff; - box-shadow: 2px 2px 4px rgb(80, 80, 80, 0.1); - text-align: left; -} - -.card-showcase-icon { - display: flex; - position: relative; - overflow: hidden; - flex: 0 0 50px; - align-items: center; - justify-content: center; - border-radius: 52px; - width: 50px; - height: 50px; - background-color: #ff773d; - color: #ffffff; - font-size: 2rem; - margin-right: 2rem; -} -.card-showcase-icon:after { - content: ""; - position: absolute; - left: 50%; - width: 50%; - height: 100%; - background-color: #000000; - opacity: 0.04; -} -.card-showcase-icon .material-icons { - font-size: inherit; -} - -.card-showcase-info h4 { - margin: 0; -} -.card-showcase-info p, .card-showcase-info ul { - color: #707070; -} -.card-showcase-info ul { - padding-left: 1.25rem; -} - -.text-small { - font-size: 0.85rem; -} - -@media (min-width: 540px) { - .home-intro { - font-size: 1.6rem; - } -} - -@media (min-width: 800px) { - .showcase-cols { - grid-template-columns: 1fr 1fr; - } -} - - - -/* ================== - Docs pages -================== */ - -a[href*="/releases/question2answer"][href$=".zip"] { - margin-left: 1rem; - padding: 0.5rem; - vertical-align: bottom; - border-radius: 8px; - background: #ff773d; - color: #fff; - font-size: 0.75rem; - text-transform: uppercase; -} -a[href*="/releases/question2answer"][href$=".zip"]:active, -a[href*="/releases/question2answer"][href$=".zip"]:hover { - background: #ff6b2b; - text-decoration: none; -} - - -/* ================== - Syntax highlighting -================== */ - -.highlight .hll { background-color: #ffffcc } -.highlight .c { color: #808080 } /* Comment */ -.highlight .err { color: #f00000; background-color: #f0a0a0 } /* Error */ -.highlight .k { color: #008000 } /* Keyword */ -.highlight .o { color: #303030 } /* Operator */ -.highlight .cm { color: #808080 } /* Comment.Multiline */ -.highlight .cp { color: #507090 } /* Comment.Preproc */ -.highlight .c1 { color: #808080 } /* Comment.Single */ -.highlight .cs { color: #cc0000 } /* Comment.Special */ -.highlight .gd { color: #a00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #ff0000 } /* Generic.Error */ -.highlight .gh { color: #000080 } /* Generic.Heading */ -.highlight .gi { color: #00a000 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #c65d09 } /* Generic.Prompt */ -/* .highlight .gs { } */ /* Generic.Strong */ -.highlight .gu { color: #800080 } /* Generic.Subheading */ -.highlight .gt { color: #0040d0 } /* Generic.Traceback */ -.highlight .kc { color: #008000 } /* Keyword.Constant */ -.highlight .kd { color: #008000 } /* Keyword.Declaration */ -.highlight .kn { color: #008000 } /* Keyword.Namespace */ -.highlight .kp { color: #003080 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000 } /* Keyword.Reserved */ -.highlight .kt { color: #303090 } /* Keyword.Type */ -.highlight .m { color: #6000e0 } /* Literal.Number */ -.highlight .s { color: #d04020 } /* Literal.String */ -.highlight .na { color: #0060b0 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #b00060 } /* Name.Class */ -.highlight .no { color: #003060 } /* Name.Constant */ -.highlight .nd { color: #505050 } /* Name.Decorator */ -.highlight .ni { color: #800000 } /* Name.Entity */ -.highlight .ne { color: #f00000 } /* Name.Exception */ -.highlight .nf { color: #0060b0 } /* Name.Function */ -.highlight .nl { color: #907000 } /* Name.Label */ -.highlight .nn { color: #0e84b5 } /* Name.Namespace */ -.highlight .nt { color: #007000 } /* Name.Tag */ -.highlight .nv { color: #906030 } /* Name.Variable */ -.highlight .ow { color: #000000 } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #6000e0 } /* Literal.Number.Float */ -.highlight .mh { color: #005080 } /* Literal.Number.Hex */ -.highlight .mi { color: #0000d0 } /* Literal.Number.Integer */ -.highlight .mo { color: #4000e0 } /* Literal.Number.Oct */ -.highlight .sb { color: #d04020 } /* Literal.String.Backtick */ -.highlight .sc { color: #0040d0 } /* Literal.String.Char */ -.highlight .sd { color: #d04020 } /* Literal.String.Doc */ -.highlight .s2 { color: #d04020 } /* Literal.String.Double */ -.highlight .se { color: #606060; } /* Literal.String.Escape */ -.highlight .sh { color: #d04020 } /* Literal.String.Heredoc */ -.highlight .si { color: #d04020 } /* Literal.String.Interpol */ -.highlight .sx { color: #d02000; color: #d04020 } /* Literal.String.Other */ -.highlight .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ -.highlight .s1 { color: #d04020 } /* Literal.String.Single */ -.highlight .ss { color: #a06000 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #306090 } /* Name.Variable.Class */ -.highlight .vg { color: #d07000 } /* Name.Variable.Global */ -.highlight .vi { color: #3030b0 } /* Name.Variable.Instance */ -.highlight .il { color: #0000d0 } /* Literal.Number.Integer.Long */ diff --git a/images/pexels-leeloothefirst-5428824-800.webp b/images/pexels-leeloothefirst-5428824-800.webp new file mode 100644 index 0000000000000000000000000000000000000000..ed8a72343a50a9babf689a75908de50d51721577 GIT binary patch literal 37258 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 literal 0 HcmV?d00001 diff --git a/images/question2answer-logo-white-350x40.webp b/images/question2answer-logo-white-350x40.webp new file mode 100644 index 0000000000000000000000000000000000000000..5ecf1e50f639f4684a4b2928e4c73a81d6d39af4 GIT binary patch literal 4236 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 literal 0 HcmV?d00001 diff --git a/images/question2answer-logo.svg b/images/question2answer-logo.svg new file mode 100644 index 0000000..fa9ba1d --- /dev/null +++ b/images/question2answer-logo.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/images/shadow.png b/images/shadow.png deleted file mode 100644 index 3af0a942c5c881173f57c096d234d005cc773ea6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52248 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%0A$_^BmNzj8HFOa~*`Wdj5!7A^xA9MaKuUC|ZB& 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 less!) to install. @@ -26,13 +27,13 @@ If you are not sure about this, please check with your web hosting provider. ## Installing Question2Answer for the first time (without single sign-on) -The instructions below are for installing Question2Answer where it manages user accounts and logins for you. If you would like Question2Answer to integrate with your existing user database and account system, see the instructions for [single sign-on](/install/single-sign-on/) installation. From version 1.4, Question2Answer also offers easy [integration with a WordPress 3.x](/install/wordpress/) site and user database. +The instructions below are for installing Question2Answer where it manages user accounts and logins for you. If you would like Question2Answer to integrate with your existing user database and account system, see the instructions for [single sign-on]({{ site.baseurl }}/install/single-sign-on/) installation. From version 1.4, Question2Answer also offers easy [integration with a WordPress 3.x]({{ site.baseurl }}/install/wordpress/) site and user database. 1. Download the [latest version of Question2Answer](https://www.question2answer.org/question2answer-latest.zip) to your computer or web server (also available [on GitHub](https://github.com/q2a/question2answer/tree/master) - make sure to use the master branch for the latest stable version). 2. Unzip the download using a tool such as [WinZip](http://www.winzip.com/) (or `unzip` in the Unix shell). -3. If you want to run a non-English site, check if the appropriate [language file](/addons/) is available. If so, download and install it in the `qa-lang` folder. If not, it's simple to [translate](/translate/) Question2Answer for yourself. +3. If you want to run a non-English site, check if the appropriate [language file]({{ site.baseurl }}/addons/) is available. If so, download and install it in the `qa-lang` folder. If not, it's simple to [translate]({{ site.baseurl }}/translate/) Question2Answer for yourself. 4. Create a MySQL database, and a MySQL user with full permissions for that database. If you're interested, the [privileges](http://dev.mysql.com/doc/mysql/en/privilege-system.html) actually needed are: CREATE, ALTER, DELETE, INSERT, SELECT, UPDATE, LOCK TABLES @@ -53,8 +54,8 @@ The instructions below are for installing Question2Answer where it manages user 10. Follow the on-screen instructions to set up your database and administrator account. That's it! -See also: [Upgrading Question2Answer](/install/upgrade/) +See also: [Upgrading Question2Answer]({{ site.baseurl }}/install/upgrade/) ## Add-Ons -See the [add-ons](/addons/) page for language files, themes and plugins created by Question2Answer users. +See the [add-ons]({{ site.baseurl }}/addons/) page for language files, themes and plugins created by Question2Answer users. diff --git a/install/optimize.md b/install/optimize.md index 10004bb..5f46c4c 100644 --- a/install/optimize.md +++ b/install/optimize.md @@ -2,6 +2,7 @@ layout: page menu: install title: "Question2Answer - Optimization" +slug: install-optimization --- # Optimizing the performance of Question2Answer diff --git a/install/security.md b/install/security.md index 453a83a..cc1849b 100644 --- a/install/security.md +++ b/install/security.md @@ -1,7 +1,8 @@ --- layout: page menu: install -title: "Question2Answer - Optimization" +title: "Question2Answer - Security" +slug: install-security --- # Making Question2Answer extra secure diff --git a/install/single-sign-on.md b/install/single-sign-on.md index 493f9be..8c553af 100644 --- a/install/single-sign-on.md +++ b/install/single-sign-on.md @@ -2,11 +2,12 @@ layout: page menu: install title: "Question2Answer - Single Sign-On Integration" +slug: install-single-sign-on --- # Installing Question2Answer with single sign-on -Question2Answer is designed to integrate with websites which have an existing user database and management system. If your site is running WordPress 3.x, you should follow the instructions for [WordPress integration](/install/wordpress/). To implement single sign-on for other sites, a small amount of PHP programming is required. Please follow the instructions below: +Question2Answer is designed to integrate with websites which have an existing user database and management system. If your site is running WordPress 3.x, you should follow the instructions for [WordPress integration]({{ site.baseurl }}/install/wordpress/). To implement single sign-on for other sites, a small amount of PHP programming is required. Please follow the instructions below: 1. Download the [latest version of Question2Answer](https://github.com/q2a/question2answer/releases) to your computer or web server (also available [on GitHub](https://github.com/q2a/question2answer)). diff --git a/install/upgrade.md b/install/upgrade.md index a2edeb9..8d19234 100644 --- a/install/upgrade.md +++ b/install/upgrade.md @@ -2,11 +2,12 @@ layout: page menu: install title: "Upgrading Question2Answer" +slug: install-upgrade --- # Upgrading to the latest version of Question2Answer -Question2Answer regularly has [new releases](/install/versions/) that fix bugs and add new features. Updating your Q2A site is usually a simple process of uploading the new files and visiting your site to check for any database upgrades. Instructions in more detail: +Question2Answer regularly has [new releases]({{ site.baseurl }}/install/versions/) that fix bugs and add new features. Updating your Q2A site is usually a simple process of uploading the new files and visiting your site to check for any database upgrades. Instructions in more detail: 1. Before upgrading, it is recommended to make a backup of your website, and of your MySQL database using `mysqldump` or a similar tool. diff --git a/install/versions/1.md b/install/versions/1.md index eacdcf8..4cab047 100644 --- a/install/versions/1.md +++ b/install/versions/1.md @@ -2,6 +2,7 @@ layout: page menu: install title: "Question2Answer - 1.x version history" +slug: install-version-history-1 --- # Version History (1.6.x and earlier) diff --git a/install/versions/index.md b/install/versions/index.md index 1cc351a..7d31e8b 100644 --- a/install/versions/index.md +++ b/install/versions/index.md @@ -2,6 +2,7 @@ layout: page menu: install title: "Question2Answer - Version History" +slug: install-version-history --- # Version History @@ -140,7 +141,7 @@ First beta of major feature release, 26th September 2017. - Performance has been improved by around 20% on average (even more on questions with many answers). - Added caching system. Currently caches question data, related questions and category list for anonymous users. Caches can be stored in files or in memory (requires Memcached PHP extension). - Added comment voting. Comments may be voted up or down, and points may be given as with answers (default is no points). -- Added ability to enable/disable plugins. For backwards compatibility, by default plugins cannot be disabled (except by removing the plugin folder entirely). Plugin developers must "opt-in" to this feature using the `load_order` property in the plugin's `metadata.json`. See the [plugin documentation](/plugins/#plugin-metadata). +- Added ability to enable/disable plugins. For backwards compatibility, by default plugins cannot be disabled (except by removing the plugin folder entirely). Plugin developers must "opt-in" to this feature using the `load_order` property in the plugin's `metadata.json`. See the [plugin documentation]({{ site.baseurl }}/plugins/#plugin-metadata). - Added support for IPv6 addresses. - Schema.org microdata is now used throughout Q2A. - Number formatting characters (decimal point, thousands separator) can be specified in language files. @@ -434,4 +435,4 @@ First community-developed version of Question2Answer, 6th November 2014. - `qa-theme-base.php/ranking_spacer`: same as above. -## [Changelog for older versions](/install/versions/1/) +## [Changelog for older versions]({{ site.baseurl }}/install/versions/1/) diff --git a/install/wordpress.md b/install/wordpress.md index eff276c..4fac3f3 100644 --- a/install/wordpress.md +++ b/install/wordpress.md @@ -2,6 +2,7 @@ layout: page menu: install title: "Question2Answer - WordPress Integration" +slug: install-wordpress --- # Installing Question2Answer with WordPress integration @@ -52,4 +53,4 @@ From version 1.4, Question2Answer offers out-of-the-box integration with your [W 8. Follow the on-screen instructions to set up your database and Question2Answer functionality. -9. You can now integrate the styling of Question2Answer with your WordPress site by [creating a Question2Answer theme](/themes/). +9. You can now integrate the styling of Question2Answer with your WordPress site by [creating a Question2Answer theme]({{ site.baseurl }}/themes/). diff --git a/js/scripts.js b/js/scripts.js new file mode 100644 index 0000000..35695a9 --- /dev/null +++ b/js/scripts.js @@ -0,0 +1,471 @@ + +// Days until next fetch for Github repository links +const daysUntilNextFetch = 90; // 3 months + +class githubList { + constructor() { + this.pluginList = []; + this.themeList = []; + } + + addListLink(listType, index, linkInfo, dateInfo) { + // Push object to array, that later will be saved locally + if(listType === 'plugins') { + this.pluginList.push({id: index.toString(), link: linkInfo.toString(), date: dateInfo.toString()}); + } else { + this.themeList.push({id: index.toString(), link: linkInfo.toString(), date: dateInfo.toString()}); + } + } + + // Store Repositories locally + savePluginsList() { + localStorage.setItem('q2adocs_gitHub_plugins', JSON.stringify(this.pluginList)); + } + + saveThemesList() { + localStorage.setItem('q2adocs_gitHub_themes', JSON.stringify(this.themeList)); + } +} + +// -------------------------------- +// Common functions, Reusable STUFF +// -------------------------------- + +// Check for Parents +const hasParent = (element, ...parents) => parents.some((parent) => parent.includes(element)); + +// Loop add class +const loopAddClass = (targetClass, addClass) => { + document.querySelectorAll(targetClass).forEach(element => { + element.classList.add(addClass); + }); +} + +// Loop remove class +const loopRemoveClass = (targetClass, removeClass) => { + document.querySelectorAll(targetClass).forEach(element => { + element.classList.remove(removeClass); + }); +} + +// Scroll to Elements +scrollToElement = (element) => { + window.scroll({ + behavior: 'smooth', + left: 0, + top: element.offsetTop - 90 + }); +} + +// ---- + +// Add current page "selected-nav" class, for the Mega Menu "Documentation" Link item +if (document.querySelectorAll('.nav-main .selected-nav').length > 0) { + document.querySelector('.mega-menu-trigger .nav-item').classList.add('selected-nav'); +} +// Add selected nav to the top level parent (second nav) +if (document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && !document.body.classList.contains('template-contribute')) { + document.querySelector('.nav-main-second .selected-nav').closest('.sub-nav').previousElementSibling.classList.add('selected-nav'); +} + +// toggle menu children +let opened = null; +const toggleVisibility = e => e.classList.toggle('display-none'); + +const handleDropdown = e => { + const clickedItem = e.parentElement.lastChild.previousSibling; + + toggleVisibility(clickedItem); + + if (!opened) { + opened = clickedItem; + } else if (opened == clickedItem) { + opened = null; + } else { + toggleVisibility(opened); + opened = clickedItem; + } +} + +const navContainer = document.querySelector('.nav-container'); +const backgroundSheet = document.querySelector('.darkPane'); +const navMain = document.querySelector('.nav-main'); +const noticeContainer = document.querySelector('.notice-container'); + +// Handle Click +const handleClick = e => { + + // Menu Trigger + if (e.target.id === 'main-nav-trigger') { + if (navContainer.classList.contains('active')) { + navContainer.classList.remove('active'); + backgroundSheet.classList.remove('active'); + } else { + navContainer.classList.add('active'); + backgroundSheet.classList.add('active'); + } + } + + // Mega Menu + if (e.target.parentElement.className.includes('mega-menu-trigger')) { + navMain.classList.toggle('display-none'); + } else if (!hasParent(e.target, '.nav-main')) { + navMain.classList.add('display-none'); + } + + // Nav Menu Children + if (e.target.className.includes('toggleChildren')) { + handleDropdown(e.target); + } else if (opened) { + toggleVisibility(opened); + opened = null; + } + + // Scroll to section on Sidebar navigation click + if (e.target.className.includes('docs-nav-item')) { + const targetDataId = e.target.getAttribute('data-attr-scroll'); + scrollToElement(document.getElementById(targetDataId)); + } + + // Go Top + if (e.target.className.includes('jump-top')) { + window.scrollTo({top: 0, behavior: 'smooth'}); + } + + // Close dark sheet + if (e.target.className.includes('darkPane')) { + if (backgroundSheet.classList.contains('active')) { + navContainer.classList.remove('active'); + backgroundSheet.classList.remove('active'); + } + } + + // Needs Page refresh after fetching + if (e.target.className.includes('close-page-status')) { + location.reload(); + } + + // Notice + if (e.target.className.includes('close-notice') || e.target.className.includes('close-sheet')) { + noticeContainer.classList.add('display-none'); + // set Notice localStorage + localStorage.q2adocsNotice = 'closed'; + } + +} // End handleClick() +document.addEventListener('click', handleClick); + +// Deal with Notice saved state +const q2adocsNotice = localStorage.getItem('q2adocs_notice'); +if (localStorage.q2adocsNotice && noticeContainer != null) { + noticeContainer.classList.add('display-none'); +} + +// Quick fix to close Mega Menu, when clicking secondary nav items +document.querySelectorAll('.nav-main-second .toggleChildren').forEach(element => { + element.addEventListener('click', (e) => { + navMain.classList.add('display-none'); + }); +}); + + +// On Scroll +const header = document.querySelector('header.header'); +const stickyPos = header ? header.offsetTop : 0; + +const jumpTopContainer = document.querySelector('.jump-top-container'); +const jumpTop = document.getElementById('jump-top'); + +// Handle Scroll +const handleScroll = (e) => { + + // Sticky Topbar + if (window.scrollY > stickyPos) { + header.classList.add('sticky'); + } else { + header.classList.remove('sticky'); + } + + if(window.screen.width > 900) { + // Get the offset position of the scroll-to-top button + const stickyJumpPos = 700; + + const jtRect = jumpTopContainer.getBoundingClientRect().top; + const jtOH = ( window.pageYOffset || jumpTopContainer.scrollTop ) - ( jumpTopContainer.clientTop || 0 ); + const winHeight = window.innerHeight - jumpTopContainer.offsetHeight; + const stopStickyJump = (jtRect + jtOH - winHeight); + + if (window.scrollY > stickyJumpPos && window.scrollY < stopStickyJump) { + jumpTop.classList.add ('active'); + } else if (window.scrollY > stopStickyJump) { + jumpTop.classList.remove('active'); + } else { + jumpTop.classList.remove('active'); + } + } + +} +handleScroll(); +window.addEventListener('scroll', handleScroll); + + +// ---------------------------- +// Single page ---------------- +// ---------------------------- + +// Docs Navigation +const articleHeaders = document.querySelectorAll('\ + .page-content h1, .page-content h2, .page-content h3, .page-content h4, .page-content h5, .page-content h6\ +'); +const docsNav = document.querySelector('.docs-nav'); + +articleHeaders.forEach(element => { + element.classList.add('sectionTitle'); + let dataId = element.getAttribute('id'); + const html = '
  • ' + element.innerHTML +'
  • '; + docsNav.insertAdjacentHTML('beforeend', html); +}); + + +const sidebarDocslinks = document.querySelectorAll('.docs-nav-item'); +const docsSection = document.querySelectorAll('.sectionTitle'); + +function sidebarLinkNav() { + if(!document.body.classList.contains('template-homepage') && sidebarDocslinks != null && docsSection != null) { + let index = docsSection.length; + + while (--index && window.scrollY + 200 < docsSection[index].offsetTop) {} + + // add the active class if within visible height of the element + if (scrollY - docsSection[index].offsetHeight < docsSection[index].offsetTop) { + sidebarDocslinks.forEach((link) => link.classList.remove('active')); + sidebarDocslinks[index].classList.add('active'); + } + } +} +sidebarLinkNav(); +window.addEventListener('scroll', sidebarLinkNav); + +const currentDate = () => { + const today = new Date(); + const yyyy = today.getFullYear(); + const mm = today.getMonth() + 1; // Months start at 0! + const dd = today.getDate(); + + return new Array(yyyy, mm, dd); +} + +// Takes one date parameter, calculates and returns the amount of days between present day +const calcDays = param => { + const todayDate = currentDate(); + const currentYear = new Date(`${todayDate[0]}/${todayDate[1]}/${todayDate[2]}`); + + let convertDate = param; + if (convertDate.indexOf('-') > -1) { + const arr = convertDate.split('-'); + convertDate = `${arr[0]}/${arr[1]}/${arr[2]}`; + } + + const repositoryYear = new Date(convertDate); + + const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds + const diffDays = Math.round(Math.abs((currentYear - repositoryYear) / oneDay)); + + return diffDays; +} + +// Calculate year gap for Github repositories +const calcYears = param => { + const diffYears = calcDays(param); + let yearGap = 0; + + if ( diffYears <= 365) { + yearGap = 1; + } else if ( diffYears > 365 && diffYears <= 730) { + yearGap = 2; + } else if ( diffYears > 730 && diffYears <= 1095) { + yearGap = 3; + } else if ( diffYears > 1095) { + yearGap = 4; + } + return yearGap; +} + +// Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) +const gitLinks = document.querySelectorAll('\ + .template-addons-plugins .page-content li a[href*="https://github.com/"],\ + .template-addons-themes .page-content li a[href*="https://github.com/"]\ +'); + +const pluginLinks = document.querySelectorAll('.template-addons-plugins .page-content li a[href*="https://github.com/"]'); +const themeLinks = document.querySelectorAll('.template-addons-themes .page-content li a[href*="https://github.com/"]'); + +if(gitLinks != null && gitLinks.length) { + + const pluginsList = new githubList(); + const themesList = new githubList(); + const isPluginsPage = (document.querySelector('.template-addons-plugins') != null ? true : false); + const isThemesPage = (document.querySelector('.template-addons-themes') != null ? true : false); + + // Set outside Fetch, because it will be reused for other functions + const githubDomain = 'https://github.com/'; + + // Fetch Links + const fetchLinks = () => { + + // Set list headers + if(isPluginsPage) { + pluginsList.addListLink('plugins', '1000', 'List_length', pluginLinks.length); + pluginsList.addListLink('plugins', '1001', 'updated', new Date()); + } else if (isThemesPage) { + themesList.addListLink('themes', '1000', 'List_length', themeLinks.length); + themesList.addListLink('themes', '1001', 'updated', new Date()); + } + + for(let i=0; i= 4; + + fetch(githubJSON) + .then(res => res.json()) + .then(jsonResponse => { + if(isRepository) { + // Add link to list + if(isPluginsPage) { + pluginsList.addListLink('plugins', [i], gitLinks[i], jsonResponse.date); + } else if (isThemesPage) { + themesList.addListLink('themes', [i], gitLinks[i], jsonResponse.date); + } + } + }) + .catch(error => { + console.log(error) + // Save Unknowns as well. Prevents null Objects. + // We can remove the display tag in the createPluginTags() functions instead, by removing the "else" statement. + if(isRepository) { + if(isPluginsPage) { + pluginsList.addListLink('plugins', [i], gitLinks[i], 'unknown'); + } else if (isThemesPage) { + themesList.addListLink('themes', [i], gitLinks[i], 'unknown'); + } + } + }) + .finally(result => { + if(isPluginsPage) { + pluginsList.savePluginsList(); + } else { + themesList.saveThemesList(); + } + }); + + } // End of for loop + + setTimeout(function(){ + document.querySelector('.page-status-container').innerHTML = '\ +
    \ + There has been an update to this page. Please reload.\ + refresh\ +
    \ + '; + }, 2000); + } + + // Get saved data from LocalStorage + const retrievedPlugins = JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins')); + const retrievedThemes = JSON.parse(localStorage.getItem('q2adocs_gitHub_themes')); + + // Retrieve single data + const singleKey = (storage) => { + return Object.keys(storage); + } + const singleValue = (storage) => { + return Object.values(storage); + } + + // Create tags for both - Plugins and Themes + const createTags = (param) => { + param.forEach((item, index) => { + + const test = param.slice(2); // Remove list header (metadata) indexes + const id = Object.values(test[index] || {} )[0]; + const link = Object.values(test[index] || {} )[1]; + const date = Object.values(test[index] || {} )[2]; + + // console.log(`${id} === ${link} === ${date}`); + + if(id != null && link != null && date != null) { + // Preppend based on stored id/index, because DOM link order may not be accurate when looping + // index order will be updated when information is stored/fetched again + if(date != 'unknown') { + const yearGapClass = 'rep-date-' + calcYears(date); + gitLinks[id].insertAdjacentHTML( + 'beforebegin', + `${date}`, + ); + } else if(date == 'unknown') { + // If response is 'bad request' or '404', show "unknown" tag + gitLinks[id].insertAdjacentHTML( + 'beforebegin', + `Unknown`, + ); + } + } + }); + } + + // Start at zero, in case not fetched yet + let pluginsListUpdated = currentDate(); + let pluginListLength = 0; + let themesListUpdated = currentDate(); + let themeListLength = 0; + + if(localStorage.q2adocs_gitHub_plugins) { + // Get saved list length for Plugins + pluginsListUpdated = singleValue(retrievedPlugins[1])[2]; + pluginsListUpdated = new Date(pluginsListUpdated).toISOString().split('T')[0]; + pluginListLength = singleValue(retrievedPlugins[0])[2]; + } + if(localStorage.q2adocs_gitHub_themes) { + // Get saved list length for Themes + themesListUpdated = singleValue(retrievedThemes[1])[2]; + themesListUpdated = new Date(themesListUpdated).toISOString().split('T')[0]; + themeListLength = singleValue(retrievedThemes[0])[2]; + } + // ---------------------------- + // Create the tags / badges --- + // ---------------------------- + + // Test remaining days + // console.log('Days passed since Plugins list updated: ' + calcDays(pluginsListUpdated)); + // console.log('Days passed since Themes list updated: ' + calcDays(themesListUpdated)); + const generateTags = () => { + if(isPluginsPage) { + // Calculate number of days until next fetch + // if "N" days have passed, or the number of links no longer matches the number of saved links, request Fetch + if(retrievedPlugins === null || calcDays(pluginsListUpdated) >= daysUntilNextFetch || pluginLinks.length != pluginListLength) { + fetchLinks(); + } + createTags(retrievedPlugins); + // console.log('retrieved Plugins Object: ', retrievedPlugins); + // console.log('github plugin links length: '+ pluginLinks.length +'; saved: ' + pluginListLength); + } else if (isThemesPage) { + if(retrievedThemes === null || calcDays(themesListUpdated) >= daysUntilNextFetch || themeLinks.length != themeListLength) { + fetchLinks(); + } + createTags(retrievedThemes); + // console.log('retrieved Themes Object: ', retrievedThemes); + // console.log('github theme links length: '+ themeLinks.length +'; saved: ' + themeListLength); + } + } + + generateTags(); + +} // End of if gitLinks.length + + + diff --git a/js/scripts2024.js b/js/scripts2024.js deleted file mode 100644 index a706c63..0000000 --- a/js/scripts2024.js +++ /dev/null @@ -1,94 +0,0 @@ -window.addEventListener('load', stickyHeader); -window.addEventListener('scroll', stickyHeader, {passive: true}); - -// Get the offset position of the navbar -var header = document.querySelector('header.header'); -var stickyPos = header ? header.offsetTop : 0; - -function stickyHeader() { - if (window.scrollY > stickyPos) { - header.classList.add('sticky'); - } else { - header.classList.remove('sticky'); - } -} - -// toggle sub-menu -let menuToggle = document.querySelector('.page-sidebar-toggle'); -menuToggle.addEventListener('click', function(ev) { - let nav = this.nextElementSibling; - if (nav.classList.contains('expanded')) - nav.classList.remove('expanded'); - else - nav.classList.add('expanded'); -}); - -// Calculate Github repository year gap, against present year -// The gap year will be appended to a class later -const calcDate = param => { - const dateObj = new Date(); - const currentYear = dateObj.getUTCFullYear(); - - const repositoryYear = param.substring(0, param.indexOf('-')); - - let yearGap = 0; - if ( currentYear - repositoryYear === 1){ - yearGap = 1; - } else if ( currentYear - repositoryYear === 2){ - yearGap = 2; - } else if ( currentYear - repositoryYear === 3){ - yearGap = 3; - } else if ( currentYear - repositoryYear >= 4){ - yearGap = 4; - } - - return yearGap; -} - -// Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) -const gitLinks = document.querySelectorAll('.page-content li a[href*="https://github.com/"]'); - -if(gitLinks.length) { - for(let i=0; i { - try { - const response = await fetch(githubJSON); - - // Checks if the Link has more than 4 'forward slashes', aka a repository, to escape Github user profiles - let isRepository = (getGithubHref.match(/\//g) || []).length >= 4; - - if(isRepository){ - if(response.ok){ - let jsonResponse = await response.json(); - // Defines class with the calculated Gap Year calcDate() - const yearGapClass = 'rep-date-' + calcDate(jsonResponse.date); - // Finally prepend the tag/badge - gitLinks[i].insertAdjacentHTML( - 'beforebegin', - `${jsonResponse.date}`, - ); - } else { - // If response is 'bad request' or '404', set an "unknown" tag - gitLinks[i].insertAdjacentHTML( - 'beforebegin', - `Unknown`, - ); - } - } - } - catch(error) { - console.log(error); - } - } - getDates(); - - } -} - - diff --git a/license.md b/license.md index d4e5175..ac6e615 100644 --- a/license.md +++ b/license.md @@ -1,6 +1,7 @@ --- layout: page title: "Question2Answer - Versions" +slug: singlepage-license --- # Question2Answer is free and open source. diff --git a/plugins/index.md b/plugins/index.md index 14417f6..22c8099 100644 --- a/plugins/index.md +++ b/plugins/index.md @@ -2,32 +2,33 @@ layout: page menu: plugins title: "Question2Answer - Developers - Creating Plugins" +slug: modules --- # Creating plugins for Question2Answer Question2Answer plugins (introduced in Q2A 1.3) allow the platform to be modified or extended without changing the Q2A core. Plugins can integrate with Q2A in one of three ways: -- **[Modules](/plugins/modules/) extend Q2A in a defined and specific way.** The following types of module are supported: - - [*page*](/plugins/modules-page/) modules add a new type of page to a Question2Answer site. - - [*login*](/plugins/modules-login/) modules allow users to log in to Q2A via an external identity provider such as Facebook. - - [*editor*](/plugins/modules-editor/) modules provide a web interface for writing and editing posts. - - [*viewer*](/plugins/modules-viewer/) modules render content as HTML or text, and often work together with an editor module. - - [*event*](/plugins/modules-event/) modules are notified when something important happens, such as content being posted or voted on. - - [*widget*](/plugins/modules-widget/) modules allow extra content to be shown on Q2A pages, as chosen by the site administrator. - - [*filter*](/plugins/modules-filter/) modules can validate and/or modify many types of user input, including the content of posts. - - [*search*](/plugins/modules-search/) modules can implement a custom indexer and/or search engine for a Q2A site. - - [*captcha*](/plugins/modules-captcha/) modules provide a web interface for human verification, such as reCAPTCHA. - - [*process*](/plugins/modules-process/) modules have the opportunity to run at specific stages of Q2A's response processing. +- **[Modules]({{ site.baseurl }}/plugins/modules/) extend Q2A in a defined and specific way.** The following types of module are supported: + - [*page*]({{ site.baseurl }}/plugins/modules-page/) modules add a new type of page to a Question2Answer site. + - [*login*]({{ site.baseurl }}/plugins/modules-login/) modules allow users to log in to Q2A via an external identity provider such as Facebook. + - [*editor*]({{ site.baseurl }}/plugins/modules-editor/) modules provide a web interface for writing and editing posts. + - [*viewer*]({{ site.baseurl }}/plugins/modules-viewer/) modules render content as HTML or text, and often work together with an editor module. + - [*event*]({{ site.baseurl }}/plugins/modules-event/) modules are notified when something important happens, such as content being posted or voted on. + - [*widget*]({{ site.baseurl }}/plugins/modules-widget/) modules allow extra content to be shown on Q2A pages, as chosen by the site administrator. + - [*filter*]({{ site.baseurl }}/plugins/modules-filter/) modules can validate and/or modify many types of user input, including the content of posts. + - [*search*]({{ site.baseurl }}/plugins/modules-search/) modules can implement a custom indexer and/or search engine for a Q2A site. + - [*captcha*]({{ site.baseurl }}/plugins/modules-captcha/) modules provide a web interface for human verification, such as reCAPTCHA. + - [*process*]({{ site.baseurl }}/plugins/modules-process/) modules have the opportunity to run at specific stages of Q2A's response processing. - Modules are the preferred method for extending Q2A - [more about modules](/plugins/modules/). + Modules are the preferred method for extending Q2A - [more about modules]({{ site.baseurl }}/plugins/modules/). -- **[Layers](/plugins/layers/) modify the HTML output for some elements of a Q2A page**. Layers work similarly to [advanced themes](/themes/), by overriding some functions in the base theme class in `qa-theme-base.php`. Unlike advanced themes, multiple layers can be installed simultaneously via their respective plugins. Layers require Q2A 1.4+. -- **[Overrides](/plugins/overrides/) allow over 150 core Q2A functions to be modified**. Core functions can be replaced by the plugin's code, or they can be wrapped, with the plugin modifying the function's inputs and/or outputs. With wrapping, multiple overrides for the same function in different plugins can be active simultaneously. Overrides require Q2A 1.5+. +- **[Layers]({{ site.baseurl }}/plugins/layers/) modify the HTML output for some elements of a Q2A page**. Layers work similarly to [advanced themes]({{ site.baseurl }}/themes/), by overriding some functions in the base theme class in `qa-theme-base.php`. Unlike advanced themes, multiple layers can be installed simultaneously via their respective plugins. Layers require Q2A 1.4+. +- **[Overrides]({{ site.baseurl }}/plugins/overrides/) allow over 150 core Q2A functions to be modified**. Core functions can be replaced by the plugin's code, or they can be wrapped, with the plugin modifying the function's inputs and/or outputs. With wrapping, multiple overrides for the same function in different plugins can be active simultaneously. Overrides require Q2A 1.5+. -The sections below explain the general principles behind plugins. You will also need to read about implementing [modules](/plugins/modules/), [layers](/plugins/layers/) and/or [overrides](/plugins/overrides/), as well as some [functions](/code/functions/) in Q2A that may be useful in developing your plugin. +The sections below explain the general principles behind plugins. You will also need to read about implementing [modules]({{ site.baseurl }}/plugins/modules/), [layers]({{ site.baseurl }}/plugins/layers/) and/or [overrides]({{ site.baseurl }}/plugins/overrides/), as well as some [functions]({{ site.baseurl }}/code/functions/) in Q2A that may be useful in developing your plugin. -An alternative is to work through the [plugin tutorial](/plugins/tutorial/), which introduces all of this material in a more gentle and gradual way. +An alternative is to work through the [plugin tutorial]({{ site.baseurl }}/plugins/tutorial/), which introduces all of this material in a more gentle and gradual way. To see some examples of plugins, modules and layers, take a look at the `qa-plugin` directory in your Q2A installation as well as the modules registered in the function `qa_register_core_modules()` in `qa-base.php` of Q2A 1.5+. @@ -41,11 +42,11 @@ Plugins for Question2Answer have the following directory structure: - The plugin directory may also contain any other files and subdirectories, including PHP, HTML, Javascript, CSS, etc... - A plugin is activated by installing its directory inside Q2A's `qa-plugin` directory. -The `qa-plugin.php` file has two main purposes. First, it registers any modules, layers or overrides provided by the plugin. Second, it contains optional metadata about the plugin and its author. As of Q2A 1.5, it is not recommended for `qa-plugin.php` to perform any other actions - if this seems necessary, please consider using a [process module](/plugins/modules-process/) instead. If you wish to use non English characters in `qa-plugin.php`, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). +The `qa-plugin.php` file has two main purposes. First, it registers any modules, layers or overrides provided by the plugin. Second, it contains optional metadata about the plugin and its author. As of Q2A 1.5, it is not recommended for `qa-plugin.php` to perform any other actions - if this seems necessary, please consider using a [process module]({{ site.baseurl }}/plugins/modules-process/) instead. If you wish to use non English characters in `qa-plugin.php`, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). ## Registering modules -Each module is implemented as a PHP `class` with specific member functions, as [detailed here](/plugins/modules/). For the sake of speed, the class definition for each module should be in a separate PHP file, since Q2A will only `include` this file when necessary. +Each module is implemented as a PHP `class` with specific member functions, as [detailed here]({{ site.baseurl }}/plugins/modules/). For the sake of speed, the class definition for each module should be in a separate PHP file, since Q2A will only `include` this file when necessary. To register a module, call `qa_register_plugin_module()` in `qa-plugin.php`, with the following parameters in order: @@ -56,18 +57,18 @@ To register a module, call `qa_register_plugin_module()` in `qa-plugin.php`, wit ## Registering layers -Each layer is implemented as a PHP file containing a fixed `class` declaration, as [described here](/plugins/layers/). To register a layer, call `qa_register_plugin_layer()` in `qa-plugin.php`, with the following parameters in order: +Each layer is implemented as a PHP file containing a fixed `class` declaration, as [described here]({{ site.baseurl }}/plugins/layers/). To register a layer, call `qa_register_plugin_layer()` in `qa-plugin.php`, with the following parameters in order: 1. The name of the PHP file in the plugin directory containing the layer's class declaration, e.g. `qa-mouseover-layer.php`. 2. A human-readable layer name, which should be descriptive and unique, e.g. `'Mouseover Layer'`. This may also be used to refer to the layer within Q2A's internals. ## Registering overrides -Overrides are implemented as PHP files which contain the replacement function definitions, as [described here](/plugins/overrides/). Your functions can call the original Q2A functions using the `_base` suffix, e.g. `qa_lang_base()`. To register a file containing overrides, call `qa_register_plugin_overrides()` in `qa-plugin.php` with the name of the overrides PHP file in the plugin directory. +Overrides are implemented as PHP files which contain the replacement function definitions, as [described here]({{ site.baseurl }}/plugins/overrides/). Your functions can call the original Q2A functions using the `_base` suffix, e.g. `qa_lang_base()`. To register a file containing overrides, call `qa_register_plugin_overrides()` in `qa-plugin.php` with the name of the overrides PHP file in the plugin directory. ## Plugin localization -Throughout Q2A, the [functions](/code/functions/) `qa_lang()` and `qa_lang_html()` are used to obtain localized language phrases. An identifier string is passed to these functions, which consists of a file prefix, a slash (`/`) and a phrase key. For example, the identifier `'main/cancel_button'` obtains the element with key `'cancel_button'` from the appropriate `qa-lang-main.php` language file. Each language file is a PHP script which returns an array mapping keys to phrases, e.g. see `qa-lang-main.php`. If you wish to use non English characters in your language files, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). +Throughout Q2A, the [functions]({{ site.baseurl }}/code/functions/) `qa_lang()` and `qa_lang_html()` are used to obtain localized language phrases. An identifier string is passed to these functions, which consists of a file prefix, a slash (`/`) and a phrase key. For example, the identifier `'main/cancel_button'` obtains the element with key `'cancel_button'` from the appropriate `qa-lang-main.php` language file. Each language file is a PHP script which returns an array mapping keys to phrases, e.g. see `qa-lang-main.php`. If you wish to use non English characters in your language files, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). From Q2A 1.5+, plugins can add their own language prefixes and phrases by calling `qa_register_plugin_phrases()` from within `qa-plugin.php`, with the following parameters in order: diff --git a/plugins/layers.md b/plugins/layers.md index d2dc554..a8f1308 100644 --- a/plugins/layers.md +++ b/plugins/layers.md @@ -2,6 +2,7 @@ layout: page menu: plugins title: "Question2Answer - Developers - Layers Modules" +slug: modules-layers --- # Implementing plugin layers @@ -16,9 +17,9 @@ class qa_html_theme_layer extends qa_html_theme_base } ``` -Layers work similarly to [advanced themes](/themes/), except multiple layers can be installed simultaneously via their respective plugins. The PHP code for each layer is automatically modified at runtime (by `qa_load_theme_class()` in `qa-app-format.php`) to build an inheritance chain of appropriately renamed classes. This process is transparent to you as a layer developer. If you wish to use non English characters in your layer, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). +Layers work similarly to [advanced themes]({{ site.baseurl }}/themes/), except multiple layers can be installed simultaneously via their respective plugins. The PHP code for each layer is automatically modified at runtime (by `qa_load_theme_class()` in `qa-app-format.php`) to build an inheritance chain of appropriately renamed classes. This process is transparent to you as a layer developer. If you wish to use non English characters in your layer, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). -For more information on how to override theme functions in your layer class declaration, **start at step 3** of the documentation for [advanced themes](/themes/). When overriding functions in your layer, you should use PHP's double colon (`::`) notation to call through to the parent class as much as possible. This will maximize the chance of your layer working well with advanced themes, layers in other plugins, and future versions of Question2Answer. +For more information on how to override theme functions in your layer class declaration, **start at step 3** of the documentation for [advanced themes]({{ site.baseurl }}/themes/). When overriding functions in your layer, you should use PHP's double colon (`::`) notation to call through to the parent class as much as possible. This will maximize the chance of your layer working well with advanced themes, layers in other plugins, and future versions of Question2Answer. Within a plugin's layer PHP file, the following pseudo-constants (substituted for strings at runtime) might prove useful: diff --git a/plugins/modules-captcha.md b/plugins/modules-captcha.md index f45a15d..d07df0b 100644 --- a/plugins/modules-captcha.md +++ b/plugins/modules-captcha.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Captcha Modules" +slug: modules-captcha --- # Captcha Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) A captcha module (requires Q2A 1.5+) provides a web interface for verifying that a user is a human, rather than an automated script generating spam. The [captcha](http://en.wikipedia.org/wiki/CAPTCHA) should provide a challenge that is easy for humans but difficult for computers, such as reading some warped text or answering a common sense question. @@ -16,4 +17,4 @@ The PHP `class` for a captcha module should contain the following functions (all - **`form_html(&$qa_content, $error)`**. This function returns the HTML form to be displayed for the captcha challenge. You can use hidden fields in this form to provide any information required by `validate_post()` - see below. If `$error` is not `null`, it contains an error message that should be displayed with the captcha, as passed out previously by your `validate_post()` function. You can also inject elements (such as Javascript) into the global `$qa_content` array for the page - see `qa-plugin/recaptcha-captcha/qa-recaptcha-captcha.php` for an example, or [ask here](http://www.question2answer.org/qa/). Note that if a captcha is required in multiple places on a web page, Q2A will only call `form_html()` once, and will take care of moving the captcha around the page as appropriate. This means your captcha must continue to work even if it was moved inside the [DOM](http://en.wikipedia.org/wiki/Document_Object_Model). - **`validate_post(&$error)`**. This function returns whether the user responded to the captcha correctly. The function should retrieve the necessary form fields from PHP's `$_POST` array or Q2A's `qa_post_text()` function, and then perform the appropriate verification. Return `true` if the verification succeeded, otherwise `false`. If verification failed, you should also set `$error` to a textual error message which will passed back to `form_html()` for display to the user. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-editor.md b/plugins/modules-editor.md index b30a38a..1f1e1d4 100644 --- a/plugins/modules-editor.md +++ b/plugins/modules-editor.md @@ -2,15 +2,16 @@ layout: page menu: plugins title: "Question2Answer - Developers - Editor Modules" +slug: modules-editor --- # Editor Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) An editor module implements a web interface for editing the content of questions, answers and comments. The module controls the HTML field which is displayed for editing, and converts the input from that field into data for storage in Q2A's database. -For example, the default editor module in `qa-editor-basic.php` displays a simple text field and generates text content for storage. The WYSIWYG Editor plugin included with Q2A wraps [CKEditor](http://ckeditor.com/) and generates text or HTML content for storage. Your editor can also store content in a format other than text or HTML, but a [viewer module](/plugins/modules-viewer/) will be required to render that content for Q2A. +For example, the default editor module in `qa-editor-basic.php` displays a simple text field and generates text content for storage. The WYSIWYG Editor plugin included with Q2A wraps [CKEditor](http://ckeditor.com/) and generates text or HTML content for storage. Your editor can also store content in a format other than text or HTML, but a [viewer module]({{ site.baseurl }}/plugins/modules-viewer/) will be required to render that content for Q2A. The PHP `class` for an editor module must contain the following functions (all are **required**): @@ -18,7 +19,7 @@ The PHP `class` for an editor module must contain the following functions (all a - **`get_field(&$qa_content, $content, $format, $fieldname, $rows)`** should return an HTML-based field for your editor. The `$content` and `$format` parameters specify the content that needs editing or `null` if new content is being created. The `$fieldname` parameter contains the HTML element name that you should use - if your editor outputs multiple HTML elements, use `$fieldname` as a prefix. The `$rows` parameter indicates a suggested height for your editor, in lines of text. To output custom HTML for your editor, return `array('type' => 'custom', 'html' => '[the html]')` from this function. You can also return an array representing any standard Q2A form field and inject elements (such as Javascript) into the `$qa_content` array - see `qa-editor-basic.php` and `qa-wysiwyg-editor.php` for some examples, or [ask here](http://www.question2answer.org/qa/). -- **`read_post($fieldname)`** should retrieve the content from your editor, as POSTed from the user's web browser, and convert it for storage in Q2A's database. The `$fieldname` parameter matches the value that was previously passed to `get_field()`. To store plain text, return `array('format' => '', 'content' => '[text in UTF-8]')` from this function. To store HTML, return `array('format' => 'html', 'content' => '[html in UTF-8]')`. **If you are storing HTML as submitted by the user's browser, you must sanitize it using `qa_sanitize_html()` to prevent Javascript injection and other security issues.** Aside from plain text and HTML, your `read_post()` function can store any other type of content in Q2A's database, by supplying an appropriate `'format'` (up to 20 ASCII characters in length) and corresponding `'content'`. In this case, a [viewer module](/plugins/modules-viewer/) will be required to render the stored content as plain text or HTML. +- **`read_post($fieldname)`** should retrieve the content from your editor, as POSTed from the user's web browser, and convert it for storage in Q2A's database. The `$fieldname` parameter matches the value that was previously passed to `get_field()`. To store plain text, return `array('format' => '', 'content' => '[text in UTF-8]')` from this function. To store HTML, return `array('format' => 'html', 'content' => '[html in UTF-8]')`. **If you are storing HTML as submitted by the user's browser, you must sanitize it using `qa_sanitize_html()` to prevent Javascript injection and other security issues.** Aside from plain text and HTML, your `read_post()` function can store any other type of content in Q2A's database, by supplying an appropriate `'format'` (up to 20 ASCII characters in length) and corresponding `'content'`. In this case, a [viewer module]({{ site.baseurl }}/plugins/modules-viewer/) will be required to render the stored content as plain text or HTML. In addition, the following **optional** functions may also be defined (all require Q2A 1.5+): @@ -28,4 +29,4 @@ In addition, the following **optional** functions may also be defined (all requi - **`update_script($fieldname)`** can return a Javascript that prepares the editor content for submission via the enclosing form (requires Q2A 1.5+). If your editor manipulates content outside the form's field, e.g. in an iframe, this allows it to update the field content immediately before the form is submitted. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-event.md b/plugins/modules-event.md index 851af9c..11a9059 100644 --- a/plugins/modules-event.md +++ b/plugins/modules-event.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Event Modules" +slug: modules-event --- # Event Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) An event module (requires Q2A 1.4+) is notified when something important happens within Q2A. It can be used to react to this event in any way it wishes. The PHP class for an event module must contain the following function to react to events: @@ -88,4 +89,4 @@ Below is a list of possible values for `$event` and the event that each value de - `'search'` when a search is performed. The search query is in `$params['query']` and the start position in `$params['start']`. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-filter.md b/plugins/modules-filter.md index 78848a3..91aea88 100644 --- a/plugins/modules-filter.md +++ b/plugins/modules-filter.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Filter Modules" +slug: modules-filter --- # Filter Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) A filter module (requires Q2A 1.5+) enables plugins to validate and/or modify many different types of user input. This includes the content of posts as well as user information such as usernames and email addresses. Filter modules can also control whether or not the post is queued for moderation. Possible applications of filter modules include cleaning up user input, restricting users of a site, creating specialized Q&A applications, and advanced spam checking. @@ -36,7 +37,7 @@ The PHP class for a filter module may contain the following functions (all are o `'editor'` - The name of the [editor module](/plugins/modules-editor/) used + The name of the [editor module]({{ site.baseurl }}/plugins/modules-editor/) used No No Maybe @@ -133,4 +134,4 @@ The PHP class for a filter module may contain the following functions (all are o - **`validate_password($password, $olduser)`**. This allows a filter module to validate an entered password for new or existing Q2A user accounts. The `$password` parameter contains the password entered. Note than unlike other filter module functions, `validate_password()` cannot modify a password, but rather only validate it. The function can declare the pssword invalid by returning a textual error, otherwise it should return `null`. For your reference, if the password is for an existing user, `$olduser` contains an array of information about the user, but this will not contain the user's previous password, since Q2A doesn't store passwords in their original form. If the password is for a new user, `$olduser` will be `null`. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-login.md b/plugins/modules-login.md index db92f47..a25427e 100644 --- a/plugins/modules-login.md +++ b/plugins/modules-login.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Login Modules" +slug: modules-login --- # Login Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) A login module enables users to log in to Q2A via an external identity provider, such as [Open ID](http://openid.net/). The module can display some HTML alongside Q2A's login and register links in the navigation menu, and/or on Q2A's login and register pages. This HTML can use a variety of methods to obtain the user's credentials, such as linking to a separate form or showing a pop-up via Javascript. @@ -39,6 +40,6 @@ The PHP `class` for a login module can contain the following functions (all are - **`logout_html($tourl)`** allows your module to display some HTML for logging out the user, to be displayed in the top right menu. Your `logout_html()` function is only called if the current Q2A user was logged in via `qa_log_in_external_user()` and your `match_source()` function returns `true` for the `$source` value that was used. The `$tourl` parameter contains the URL of the Q2A logout page, to which you **must** redirect the user once your own logout process is complete. -You may choose to request the user's external login credentials on a page which is served by Q2A. In this case you would include both a login module and a [page module](/plugins/modules-page/) in your plugin. The HTML displayed by the login module would link to your custom page, and the page module would display the appropriate form and call `qa_log_in_external_user()` when the form was submitted. +You may choose to request the user's external login credentials on a page which is served by Q2A. In this case you would include both a login module and a [page module]({{ site.baseurl }}/plugins/modules-page/) in your plugin. The HTML displayed by the login module would link to your custom page, and the page module would display the appropriate form and call `qa_log_in_external_user()` when the form was submitted. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-page.md b/plugins/modules-page.md index 19dd255..96bde04 100644 --- a/plugins/modules-page.md +++ b/plugins/modules-page.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Plugin Modules" +slug: modules-page --- # Page Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) A page module adds one or more new pages to a Q2A installation. In Q2A, every page displayed has a corresponding `$request` string. For example, the `$request` for Q2A's page for user John Doe is `'user/John Doe'`. Page modules are able to control the output for particular `$request` strings in the same way. (Depending on the chosen URL structure for a Q2A installation, `$request` can be represented in different ways on the actual URL - you need not worry about this, nor should you make any assumptions.) @@ -26,8 +27,8 @@ The PHP `class` for a page module can contain the following functions (all are o - All elements within the nested `$qa_content` array should be made HTML safe - use `qa_html()` as appropriate. - Use `$qa_content['title']` and `$qa_content['error']` to display a page title or error message respectively. - To add some custom HTML to the page, add an element to `$qa_content` whose key begins with `custom`, e.g. `$qa_content['custom']` or `$qa_content['custom_2']`. The value of the element should contain the actual HTML. - - To add a Q2A-style form to the page, add an element to `$qa_content` whose key begins with `form`, e.g. `$qa_content['form']` or `$qa_content['form_2']`. The value of the element should contain a Q2A form array, whose definition is also beyond the scope of this documentation, although see the [comments here](/plugins/modules/). + - To add a Q2A-style form to the page, add an element to `$qa_content` whose key begins with `form`, e.g. `$qa_content['form']` or `$qa_content['form_2']`. The value of the element should contain a Q2A form array, whose definition is also beyond the scope of this documentation, although see the [comments here]({{ site.baseurl }}/plugins/modules/). - Other possible types of structured content in `$qa_content` are question lists (keys starting `q_list`), question views (`q_view`), answer lists (`a_list`) and rankings (`ranking`). Look through the `qa-page-*.php` files for examples. - The elements in `$qa_content` are displayed in array order, with a few exceptions such as page titles and errors. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-process.md b/plugins/modules-process.md index f99a98a..bdde802 100644 --- a/plugins/modules-process.md +++ b/plugins/modules-process.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Process Modules" +slug: modules-process --- # Process Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) A process module (requires Q2A 1.5+) can perform any task at specific stages of Q2A's response processing. For example, process modules could provide logging, implement a page cache, change database settings, or perform extra user authentication. @@ -32,4 +33,4 @@ The PHP `class` for a process module can define one or more of the following fun - **`shutdown($reason)`** will be called at the end of Q2A's response processing. The `$reason` parameter will be `'error'` if an error occurred, `'redirect'` if there was a redirection, otherwise it will be `null`. More values may be added in future. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-search.md b/plugins/modules-search.md index c271454..4947edf 100644 --- a/plugins/modules-search.md +++ b/plugins/modules-search.md @@ -2,17 +2,18 @@ layout: page menu: plugins title: "Question2Answer - Developers - Search Modules" +slug: modules-search --- # Search Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) A search module (requires Q2A 1.5+) enables plugins to create a custom index of the content in a Q2A site, including questions, answers, comments and custom pages. If a search module is selected by the site administrator, it can replace the default Q2A search results with its own, and optionally include pages from external websites in those results. The PHP class for a search module may contain the following functions (all are optional): -- **`index_post($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid)`**. If defined, this function is called when a post should be added to the search index, either because it is new, or because it has just been edited. The parameter `$postid` contains the `postid` from the database. The `$type` will be `'Q'` for questions, `'A'` for answers, `'C'` for comments or `'NOTE'` for a note (currently used for question closing explanations). The `$questionid` is the `postid` of the question to which the post belongs and the `$parentid` is the `postid` of its immediate parent, or `null` if it has none. For questions, `$title` contains the title in UTF-8 format, otherwise it is `null`. The `$content` and `$format` parameters describe the post's content. If `$format` is `''`, then `$content` contains plain text in UTF-8 encoding. If `$format` is `'html'`, then `$content` contains HTML with UTF-8 encoding. Other values of `$format` may be present if a non-standard [editor module](/plugins/modules-editor/) is installed. The `$text` parameter contains a UTF-8 plain text rendering of the post, which is particularly useful for building a search index. For questions, `$tagstring` contains a comma-separated list of tags, otherwise it is `null`. Finally, `$categoryid` contains the category ID of the post, or `null` if there is none. Note that `index_post()` is only called when a post has been made visible on the Q2A site - it is not called if a new post was marked for moderation. +- **`index_post($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid)`**. If defined, this function is called when a post should be added to the search index, either because it is new, or because it has just been edited. The parameter `$postid` contains the `postid` from the database. The `$type` will be `'Q'` for questions, `'A'` for answers, `'C'` for comments or `'NOTE'` for a note (currently used for question closing explanations). The `$questionid` is the `postid` of the question to which the post belongs and the `$parentid` is the `postid` of its immediate parent, or `null` if it has none. For questions, `$title` contains the title in UTF-8 format, otherwise it is `null`. The `$content` and `$format` parameters describe the post's content. If `$format` is `''`, then `$content` contains plain text in UTF-8 encoding. If `$format` is `'html'`, then `$content` contains HTML with UTF-8 encoding. Other values of `$format` may be present if a non-standard [editor module]({{ site.baseurl }}/plugins/modules-editor/) is installed. The `$text` parameter contains a UTF-8 plain text rendering of the post, which is particularly useful for building a search index. For questions, `$tagstring` contains a comma-separated list of tags, otherwise it is `null`. Finally, `$categoryid` contains the category ID of the post, or `null` if there is none. Note that `index_post()` is only called when a post has been made visible on the Q2A site - it is not called if a new post was marked for moderation. - **`unindex_post($postid)`**. If defined, this function is called when a post should be removed from the search index, because it has been hidden, deleted, or is about to be edited (in which case `index_post()` will be called immediately after). Generally, the parameter `$postid` matches a value previously passed to `index_post()` when the post was indexed. However in some circumstances the `$postid` might never have been indexed, in which case the function should do nothing. @@ -33,4 +34,4 @@ The PHP class for a search module may contain the following functions (all are o To save additional database queries, it is also possible to pass through more information about a matching question, post or page in `'question'`, `'match_type'` or `'page'` elements. This requires knowledge of Q2A's internals - see `qa-app-search.php` for more information on how these elements are used with the `$fullcontent` parameter. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-viewer.md b/plugins/modules-viewer.md index 925dc7a..47652b7 100644 --- a/plugins/modules-viewer.md +++ b/plugins/modules-viewer.md @@ -2,13 +2,14 @@ layout: page menu: plugins title: "Question2Answer - Developers - Viewer Modules" +slug: modules-viewer --- # Viewer Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) -A viewer module takes content stored in the database, as generated by an [editor module](/plugins/modules-editor/), and renders it for display by Q2A. The default viewer module in `qa-viewer-basic.php` takes care of rendering stored text or HTML, so you will only need to create a viewer module if you have an editor module which stores content in a different format. +A viewer module takes content stored in the database, as generated by an [editor module]({{ site.baseurl }}/plugins/modules-editor/), and renders it for display by Q2A. The default viewer module in `qa-viewer-basic.php` takes care of rendering stored text or HTML, so you will only need to create a viewer module if you have an editor module which stores content in a different format. The PHP `class` for a viewer module must contain the following functions (all are **required**): @@ -22,4 +23,4 @@ The PHP `class` for a viewer module must contain the following functions (all ar - **`get_text($content, $format, $options)`** should take the supplied `$content` in `$format` and render it as plain text with UTF-8 encoding. The text is primarily used in notification emails and for search indexing. `$options['blockwordspreg']` should be treated in the same way as for `get_html()`. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules-widget.md b/plugins/modules-widget.md index 522eac9..2aded90 100644 --- a/plugins/modules-widget.md +++ b/plugins/modules-widget.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Widget Modules" +slug: modules-widget --- # Widget Modules -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) A widget module (requires Q2A 1.4+) displays some extra HTML content on one or more of Q2A's pages. The widget specifies which types of pages ("templates") it can be displayed on, and where it can be displayed on the page. The Q2A site admin then chooses exactly where and when the widget is shown, out of those options that the widget makes available. @@ -52,4 +53,4 @@ The PHP class for a widget module must contain the following functions (all are - `$request` - the Q2A request that generated the page, e.g. `'user/John Doe'`. The `$request` uses slash as a separator, independent of the type of URLs a particular Q2A site is using. - `$qa_content` - the nested Q2A array containing the full content for the page being displayed, as it was passed to the theme object. Use PHP's `print_r()` function to see what's inside and find any bits you need. -[« Back to modules](/plugins/modules/) +[« Back to modules]({{ site.baseurl }}/plugins/modules/) diff --git a/plugins/modules.md b/plugins/modules.md index 64db497..7b4cba2 100644 --- a/plugins/modules.md +++ b/plugins/modules.md @@ -2,26 +2,27 @@ layout: page menu: plugins title: "Question2Answer - Developers - Plugin Modules" +slug: modules-modules --- # Implementing plugin modules -Plugin modules enable Question2Answer to be extended in specific ways. Each module is implemented as a PHP `class` with specific member functions. Some functions are relevant for all module types, as described below. Other functions are specific to a certain type of module, e.g. [page modules](/plugins/modules-page/) implement the functions `match_request()` and `process_request()`. If you wish to use non English characters in the PHP code for a module, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). +Plugin modules enable Question2Answer to be extended in specific ways. Each module is implemented as a PHP `class` with specific member functions. Some functions are relevant for all module types, as described below. Other functions are specific to a certain type of module, e.g. [page modules]({{ site.baseurl }}/plugins/modules-page/) implement the functions `match_request()` and `process_request()`. If you wish to use non English characters in the PHP code for a module, ensure your text editor is using UTF-8 encoding without a BOM (byte order mark). ## Types of Module Below is a list of the module types supported by Q2A - click for more information about each type: -- [**page**](/plugins/modules-page/) modules add a new type of page to a Q2A installation. The XML Sitemap plugin included with Q2A is an example. -- [**login**](/plugins/modules-login/) modules allow users to log in to Q2A via an external identity provider, like the Facebook Login plugin included with Q2A. -- [**editor**](/plugins/modules-editor/) modules provide a web interface for writing and editing posts. The WYSIWYG Editor included with Q2A is implemented via a plugin that registers an editor module. -- [**viewer**](/plugins/modules-viewer/) modules render content as HTML or text, and often work together with an editor module. Support for [Markdown](http://en.wikipedia.org/wiki/Markdown) or equations could be added via a plugin with both an editor and viewer module. -- [**event**](/plugins/modules-event/) modules (from Q2A 1.4+) are notified when something important happens, such as content being posted or voted on. They can be used for analytics, notifications or integrating with external services such as [Twitter](http://www.twitter.com/). The Event Logger plugin included with Q2A logs all events to a database table and/or log files. -- [**widget**](/plugins/modules-widget/) modules (from Q2A 1.4+) allow extra content to be shown on Q2A pages. The site admin can decide exactly where the widget is displayed. The Tag Cloud widget plugin that comes with Q2A is one example. -- [**filter**](/plugins/modules-filter/) modules (from Q2A 1.5+) can validate and/or modify many types of user input, including the content of posts. They can also mark whether a particular post should be queued for moderation. -- [**search**](/plugins/modules-search/) modules (from Q2A 1.5+) can implement a custom indexer and/or search engine for a Q2A site. They are given the opportunity to index posts and/or custom page content, and replace the default search results with their own. -- [**captcha**](/plugins/modules-captcha/) modules (from Q2A 1.5+) provide a web interface for human verification. The reCAPTCHA plugin that comes with Q2A provides an example, using the popular [reCAPTCHA](http://www.google.com/recaptcha) service. -- [**process**](/plugins/modules-process/) modules (from Q2A 1.5+) have the opportunity to run at specific stages of Q2A's response processing. They can be used for logging, analytics, additional authentication, caching, etc... +- [**page**]({{ site.baseurl }}/plugins/modules-page/) modules add a new type of page to a Q2A installation. The XML Sitemap plugin included with Q2A is an example. +- [**login**]({{ site.baseurl }}/plugins/modules-login/) modules allow users to log in to Q2A via an external identity provider, like the Facebook Login plugin included with Q2A. +- [**editor**]({{ site.baseurl }}/plugins/modules-editor/) modules provide a web interface for writing and editing posts. The WYSIWYG Editor included with Q2A is implemented via a plugin that registers an editor module. +- [**viewer**]({{ site.baseurl }}/plugins/modules-viewer/) modules render content as HTML or text, and often work together with an editor module. Support for [Markdown](http://en.wikipedia.org/wiki/Markdown) or equations could be added via a plugin with both an editor and viewer module. +- [**event**]({{ site.baseurl }}/plugins/modules-event/) modules (from Q2A 1.4+) are notified when something important happens, such as content being posted or voted on. They can be used for analytics, notifications or integrating with external services such as [Twitter](http://www.twitter.com/). The Event Logger plugin included with Q2A logs all events to a database table and/or log files. +- [**widget**]({{ site.baseurl }}/plugins/modules-widget/) modules (from Q2A 1.4+) allow extra content to be shown on Q2A pages. The site admin can decide exactly where the widget is displayed. The Tag Cloud widget plugin that comes with Q2A is one example. +- [**filter**]({{ site.baseurl }}/plugins/modules-filter/) modules (from Q2A 1.5+) can validate and/or modify many types of user input, including the content of posts. They can also mark whether a particular post should be queued for moderation. +- [**search**]({{ site.baseurl }}/plugins/modules-search/) modules (from Q2A 1.5+) can implement a custom indexer and/or search engine for a Q2A site. They are given the opportunity to index posts and/or custom page content, and replace the default search results with their own. +- [**captcha**]({{ site.baseurl }}/plugins/modules-captcha/) modules (from Q2A 1.5+) provide a web interface for human verification. The reCAPTCHA plugin that comes with Q2A provides an example, using the popular [reCAPTCHA](http://www.google.com/recaptcha) service. +- [**process**]({{ site.baseurl }}/plugins/modules-process/) modules (from Q2A 1.5+) have the opportunity to run at specific stages of Q2A's response processing. They can be used for logging, analytics, additional authentication, caching, etc... ## Common module functions @@ -37,11 +38,11 @@ Some functions are relevant for all module types, as listed below. You may also - All elements within the nested form array should be made HTML safe - use `qa_html()` as appropriate. - The `NAME=...` of all buttons and fields (within `tags` elements) should have a prefix which is unique to your plugin. - - Use `qa_clicked()` and `qa_post_text()`, as described [here](/code/functions/), to check for button clicks and retrieve submitted fields. + - Use `qa_clicked()` and `qa_post_text()`, as described [here]({{ site.baseurl }}/code/functions/), to check for button clicks and retrieve submitted fields. - Use `qa_opt()` to get or set an option that is stored in Q2A's options table. To prevent interference with Q2A or other plugins, any options should be named using a prefix which is unique to your plugin. - **`init_queries($tableslc)`**. If a module requires some database initialization, e.g. to create its own table, it can define this function to integrate into Q2A's installation and updating process (requires Q2A 1.5+). For your convenience, `$tableslc` contains an array of lower case names of tables currently in the Q2A database. Your function should examine the `$tableslc` array and perform any other diagnostics in order to determine whether initialization is required. If so, return an array of one or more MySQL queries that should be run, otherwise return `null`. If you are creating a table with a `CREATE TABLE` query, you should add the appropriate prefix to the table name - see `qa_db_add_table_prefix()` in `qa-db.php` or use the `^` symbol before the table name. This will prevent clashes between separate Q2A installations sharing a database. ## Communication between modules -A module can define any additional functions for its own purposes, in the usual way for a PHP class. These functions can also be used for communication between modules, either within a plugin, or between different plugins. To call a function defined in a module, call [`qa_load_module()`](/code/functions/) to retrieve an object of the module's class, then call the object's function in the usual PHP way. Note that multiple calls to `qa_load_module()` will return the same object. +A module can define any additional functions for its own purposes, in the usual way for a PHP class. These functions can also be used for communication between modules, either within a plugin, or between different plugins. To call a function defined in a module, call [`qa_load_module()`]({{ site.baseurl }}/code/functions/) to retrieve an object of the module's class, then call the object's function in the usual PHP way. Note that multiple calls to `qa_load_module()` will return the same object. diff --git a/plugins/overrides.md b/plugins/overrides.md index fb0d16b..2d23efe 100644 --- a/plugins/overrides.md +++ b/plugins/overrides.md @@ -2,11 +2,12 @@ layout: page menu: plugins title: "Question2Answer - Developers - Overrides" +slug: overrides --- # Implementing plugin overrides -Plugin overrides (requires Q2A 1.5+) can replace or wrap over 150 functions defined within the Q2A core. Overrides can be very powerful, but they run a risk of incompatibility with future versions of Q2A. If possible, it is recommended to use [modules](/plugins/modules/) or [layers](/plugins/layers/) instead. Nonetheless, some things can only be done via overrides, and future versions of Q2A will try not to break them. +Plugin overrides (requires Q2A 1.5+) can replace or wrap over 150 functions defined within the Q2A core. Overrides can be very powerful, but they run a risk of incompatibility with future versions of Q2A. If possible, it is recommended to use [modules]({{ site.baseurl }}/plugins/modules/) or [layers]({{ site.baseurl }}/plugins/layers/) instead. Nonetheless, some things can only be done via overrides, and future versions of Q2A will try not to break them. Not all functions in Q2A can be overridden. To check a particular function, see whether its definition begins with: diff --git a/plugins/tutorial.md b/plugins/tutorial.md index 68cbd30..1553654 100644 --- a/plugins/tutorial.md +++ b/plugins/tutorial.md @@ -2,13 +2,14 @@ layout: page menu: plugins title: "Question2Answer - Developers - Tutorial - Writing a Plugin" +slug: tutorial --- # Tutorial: Writing a Question2Answer plugin -In this tutorial, we will create a Question2Answer (1.5+) [plugin](/plugins/) from scratch. Our plugin will allow descriptions to be added to tags on a Q2A site. These descriptions will be displayed and edited on the page listing recent questions for each tag. They will also be shown in a tooltip when users mouse over a tag. The Q2A administrator will be able to decide who can edit tag descriptions. +In this tutorial, we will create a Question2Answer (1.5+) [plugin]({{ site.baseurl }}/plugins/) from scratch. Our plugin will allow descriptions to be added to tags on a Q2A site. These descriptions will be displayed and edited on the page listing recent questions for each tag. They will also be shown in a tooltip when users mouse over a tag. The Q2A administrator will be able to decide who can edit tag descriptions. -The tutorial will touch on each of the main areas of Q2A plugin `function`ality. Tag descriptions will be stored in the Q2A database, using Q2A's tag metadata table. The descriptions will be displayed on a tag's page using a [widget module](/plugins/modules-widget/). Tags will be edited on pages provided by a [page module](/plugins/modules-page/). An [override](/plugins/overrides/) will be used to show the tooltips, and then a [layer](/plugins/layers/) will allow us to do this with fewer database queries. An admin form for the plugin will be displayed in the 'Plugins' section of the admin panel, with the corresponding settings stored by Q2A's options mechanism. At the end of this process, we will see how to make the plugin localizable. +The tutorial will touch on each of the main areas of Q2A plugin `function`ality. Tag descriptions will be stored in the Q2A database, using Q2A's tag metadata table. The descriptions will be displayed on a tag's page using a [widget module]({{ site.baseurl }}/plugins/modules-widget/). Tags will be edited on pages provided by a [page module]({{ site.baseurl }}/plugins/modules-page/). An [override]({{ site.baseurl }}/plugins/overrides/) will be used to show the tooltips, and then a [layer]({{ site.baseurl }}/plugins/layers/) will allow us to do this with fewer database queries. An admin form for the plugin will be displayed in the 'Plugins' section of the admin panel, with the corresponding settings stored by Q2A's options mechanism. At the end of this process, we will see how to make the plugin localizable. This tutorial assumes you are familiar with the [PHP](http://www.php.net/) programming language as well as some basic [HTML](http://en.wikipedia.org/wiki/HTML) and [MySQL](http://www.mysql.com/). @@ -17,7 +18,7 @@ This tutorial assumes you are familiar with the [PHP](http://www.php.net/) progr In this first step we will set up the directory for the plugin and create the crucial `qa-plugin.php` file within. -- Create a new empty directory `tag-descriptions` in Q2A's `qa-plugin` directory - see [here](/plugins/) for naming conventions. +- Create a new empty directory `tag-descriptions` in Q2A's `qa-plugin` directory - see [here]({{ site.baseurl }}/plugins/) for naming conventions. - Create an empty `qa-plugin.php` file inside this directory. This file is the core of the plugin. It registers each of the plugin's elements with Q2A and contains meta information for display in Q2A's admin interface. @@ -46,7 +47,7 @@ In this first step we will set up the directory for the plugin and create the cr } ``` - See [here](/plugins/) for a detailed description of the metadata fields. The code below the metadata ensures that visitors to your Q2A site cannot request the `qa-plugin.php` page directly (even though doing so would cause no actual harm). + See [here]({{ site.baseurl }}/plugins/) for a detailed description of the metadata fields. The code below the metadata ensures that visitors to your Q2A site cannot request the `qa-plugin.php` page directly (even though doing so would cause no actual harm). - Reopen the 'Plugins' page of your Q2A site admin panel and check that the listing has been updated. We're on our way! @@ -70,7 +71,7 @@ In this step we will create the initial version of the widget module which displ } ``` - This is the beginning of the PHP class that will define the widget's functionality. The `allow_template()` function is one of three class functions required in a widget module ([more here](/plugins/modules-widget/)). It is called by Q2A to determine which types of pages the widget can be displayed on. In this case, the widget can only be displayed on pages listing recent questions for a tag, so the function only returns `true` if the `$template` parameter is `'tag'`. + This is the beginning of the PHP class that will define the widget's functionality. The `allow_template()` function is one of three class functions required in a widget module ([more here]({{ site.baseurl }}/plugins/modules-widget/)). It is called by Q2A to determine which types of pages the widget can be displayed on. In this case, the widget can only be displayed on pages listing recent questions for a tag, so the function only returns `true` if the `$template` parameter is `'tag'`. - Add the following two additional functions **inside** the `class` definition in `qa-tag-desc-widget.php`: @@ -99,7 +100,7 @@ In this step we will create the initial version of the widget module which displ ); ``` - Each parameter to `qa_register_plugin_module()` is explained by a comment in the code above - [more here](/plugins/). + Each parameter to `qa_register_plugin_module()` is explained by a comment in the code above - [more here]({{ site.baseurl }}/plugins/). - Now it's time to test out the widget. Ensure you have saved/uploaded the `qa-plugin.php` and `qa-tag-desc-widget.php` files. Then open the 'Layout' section of your Q2A site admin panel. The list of available widgets should include the name 'Tag Descriptions'. Click 'add widget' next to it, and choose to display the widget somewhere on tag pages. Note how the options shown in the admin panel reflect the widget module's responses from `allow_template()` and `allow_region()`. @@ -132,7 +133,7 @@ In this step we will create the page module that allows tag descriptions to be e } ``` - The `match_request()` function let a page module decide if it will process a particular web page request. The `$request` parameter is always slash-separated, independent of the URL structure used by a particular Q2A site - [more here](/plugins/modules-page/). We have decided that the URLs for our tag description editor will take the form `tag-edit/*` where `*` is the tag to be edited. For now, the `process_request()` function in the page module generates an empty page for these requests. + The `match_request()` function let a page module decide if it will process a particular web page request. The `$request` parameter is always slash-separated, independent of the URL structure used by a particular Q2A site - [more here]({{ site.baseurl }}/plugins/modules-page/). We have decided that the URLs for our tag description editor will take the form `tag-edit/*` where `*` is the tag to be edited. For now, the `process_request()` function in the page module generates an empty page for these requests. - Activate the page model within the plugin by pasting the following code at the end of your `qa-plugin.php` file: @@ -203,7 +204,7 @@ In this step we will get the tag description editing page up and working. There is a lot happening here, so let's go through it step by step: - - First, we include the Q2A core file `qa-db-metas.php`, since we will need use some of its functions to access the Q2A database table for tag meta information. (If necessary, modules can also create their own database tables by implementing the `init_queries()` function - [more here](/plugins/modules/)). + - First, we include the Q2A core file `qa-db-metas.php`, since we will need use some of its functions to access the Q2A database table for tag meta information. (If necessary, modules can also create their own database tables by implementing the `init_queries()` function - [more here]({{ site.baseurl }}/plugins/modules/)). - The main work consists of adding an element with key `'form'` to the `$qa_content` array. This element describes a form on the page, which is converted by the Q2A theme class into appropriate HTML code. (To add more forms to the page, more elements could be added with keys `'form2'`, `'form_extra'`, or anything else beginning with `'form'`.) @@ -268,7 +269,7 @@ In this step we will get the tag description editing page up and working. } ``` - The `output_widget()` function now identifies which tag page is being requested, retrieves the appropriate tag description from the database, and displays it if it exists, after HTML escaping. An appropriate link to edit the description is also shown, using Q2A's `qa_path_html(...)` function to create an HTML-escaped relative URL to the tag editor page - [more here](/code/functions/). + The `output_widget()` function now identifies which tag page is being requested, retrieves the appropriate tag description from the database, and displays it if it exists, after HTML escaping. An appropriate link to edit the description is also shown, using Q2A's `qa_path_html(...)` function to create an HTML-escaped relative URL to the tag editor page - [more here]({{ site.baseurl }}/code/functions/). - Save/upload as usual. If everything is in place, you should be able to view your descriptions on the tag page, click to edit them on a different page, and then click to save and view them back again on the tag page. Congratulations! @@ -290,7 +291,7 @@ In this step we will implement the tooltips which display tag descriptions when } ``` - This `qa_tag_html()` function will override the standard version of that function, which we saw just before. However our version makes uses of the standard function by calling it using the name `qa_tag_html_base()` - [more here](/plugins/overrides/). Instead of passing through the `$tag` as is, we add underscores before and after it, so that we can test that the override is working. Note that the optional `$favorited` parameter was added in Q2A 1.6, but this code will still work fine in Q2A 1.5.x, with PHP ignoring the extra unexpected parameter. + This `qa_tag_html()` function will override the standard version of that function, which we saw just before. However our version makes uses of the standard function by calling it using the name `qa_tag_html_base()` - [more here]({{ site.baseurl }}/plugins/overrides/). Instead of passing through the `$tag` as is, we add underscores before and after it, so that we can test that the override is working. Note that the optional `$favorited` parameter was added in Q2A 1.6, but this code will still work fine in Q2A 1.5.x, with PHP ignoring the extra unexpected parameter. - Activate the function override by pasting the following line at the end of your plugin's `qa-plugin.php`: @@ -326,7 +327,7 @@ In this step we will implement the tooltips which display tag descriptions when Let's explain this function step by step: - - First, we call the standard version of the function using `qa_tag_html_base()` and store the standard HTML to represent a tag in the variable `$taghtml`. By calling through to the base function rather than copying its code here, we protect ourselves better against changes in future versions of Q2A. We also enable multiple plugins to override the same function for different purposes, with Q2A building an appropriate chain of renamed functions that call each other in turn. If possible, it is always a good idea to do this when overriding a function - [more here](/plugins/overrides/). + - First, we call the standard version of the function using `qa_tag_html_base()` and store the standard HTML to represent a tag in the variable `$taghtml`. By calling through to the base function rather than copying its code here, we protect ourselves better against changes in future versions of Q2A. We also enable multiple plugins to override the same function for different purposes, with Q2A building an appropriate chain of renamed functions that call each other in turn. If possible, it is always a good idea to do this when overriding a function - [more here]({{ site.baseurl }}/plugins/overrides/). - We retrieve the description for `$tag` from the database, using `qa_db_tagmeta_get()` in the same way as before. @@ -413,7 +414,7 @@ In the second stage, a layer will replace the `post_tag_item()` function in Q2A' - Then `$plugin_tag_desc_list` is set to `null` so that this branch is skipped next time `post_tag_item(...)` is called. - - Finally, we call through to the `post_tag_item()` function in Q2A's base theme class using PHP's double colon notation - [more here](/plugins/layers/). Note how base functions are called differently for overrides and for layers. + - Finally, we call through to the `post_tag_item()` function in Q2A's base theme class using PHP's double colon notation - [more here]({{ site.baseurl }}/plugins/layers/). Note how base functions are called differently for overrides and for layers. - As with modules and overrides, we need to activate the layer by adding this in our plugin's `qa-plugin.php` file: @@ -457,7 +458,7 @@ In the second stage, a layer will replace the `post_tag_item()` function in Q2A' - As before, we check if the global `$plugin_tag_desc_list` array variable contains any items, If so, we need to retrieve some tag descriptions from the database. - - We retrieve all the tag descriptions in one database query. There is no function to retrieve metadata for multiple tags in `qa-db-metas.php`, so we write and run the query ourselves using Q2A's `qa_db_query_sub()` function. This substitutes `^` in the query string for the Q2A table prefix set in `qa-config.php`. It also substitutes `$` and `#` for any additional parameters passed, escaping them to ensure the SQL is secure - [more here](/code/functions/#database-access-in-qa-dbphp). In this case, the `$` symbol will be turning into a comma-separated list of tags in `array_keys($plugin_tag_desc_list)`. + - We retrieve all the tag descriptions in one database query. There is no function to retrieve metadata for multiple tags in `qa-db-metas.php`, so we write and run the query ourselves using Q2A's `qa_db_query_sub()` function. This substitutes `^` in the query string for the Q2A table prefix set in `qa-config.php`. It also substitutes `$` and `#` for any additional parameters passed, escaping them to ensure the SQL is secure - [more here]({{ site.baseurl }}/code/functions/#database-access-in-qa-dbphp). In this case, the `$` symbol will be turning into a comma-separated list of tags in `array_keys($plugin_tag_desc_list)`. - The `$result` obtained from the database query is turned into a map from tags to descriptions using Q2A's `qa_db_read_all_assoc()` function - see the function's definition in `qa-db.php`. The map is stored in the global `$plugin_tag_desc_map` variable so that it is available every time our `post_tag_item()` function is called. @@ -539,7 +540,7 @@ In this step, we will add an options form to the plugin which controls how the t The `admin_form()` function can also be added to any module, and enables that module to display a form on the 'Plugins' page of the Q2A admin panel. The function returns a Q2A form definition array for display on the page, after processing any appropriate user input. Most of this should be familiar from our earlier encounter with a Q2A form, but a few things are new: - - We're using Q2A's `qa_opt()` function to both set and retrieve option values - [more here](/code/functions/). + - We're using Q2A's `qa_opt()` function to both set and retrieve option values - [more here]({{ site.baseurl }}/code/functions/). - An element `'ok'` is used to add a confirmation message at the top of the form. - An element `'suffix'` is used within field definitions to show some text after the field. @@ -684,7 +685,7 @@ In this final step we will make your plugin ready for localization into differen ); ``` - This sets up localization for your plugin. The `*` in the file name passed to `qa_register_plugin_phrases()` will be substituted for language codes such as `fr` or `ru`, or `default` to load the default phrases. The second parameter `'plugin_tag_desc'` sets the prefix for retrieving your language strings using Q2A's `qa_lang()` or `qa_lang_html()` functions - [more here](/plugins/). + This sets up localization for your plugin. The `*` in the file name passed to `qa_register_plugin_phrases()` will be substituted for language codes such as `fr` or `ru`, or `default` to load the default phrases. The second parameter `'plugin_tag_desc'` sets the prefix for retrieving your language strings using Q2A's `qa_lang()` or `qa_lang_html()` functions - [more here]({{ site.baseurl }}/plugins/). - Find the line in `qa-tag-desc-widget.php` containing the phrase `Create tag description` and replace it with: @@ -746,4 +747,4 @@ If you made it this far, congratulations! This completes the tutorial. To recap, To check you followed the instructions correctly, you can [download the completed plugin](http://www.question2answer.org/releases/q2a-tag-descriptions-tutorial.zip). -Now you can think about the plugin that **you** want to build and how to go about it. To get started, take a look at the different [types of module](/plugins/modules/) that Q2A supports - as of Q2A 1.5, there are ten available, and we only covered two in this tutorial. You should also take a look at this list of useful [Q2A functions](/code/functions/) and have a browse around the Q2A source code to learn about many more. Finally, take a look at the code of the [many plugins](/addons/) already developed for Q2A. Thank you and good luck! +Now you can think about the plugin that **you** want to build and how to go about it. To get started, take a look at the different [types of module]({{ site.baseurl }}/plugins/modules/) that Q2A supports - as of Q2A 1.5, there are ten available, and we only covered two in this tutorial. You should also take a look at this list of useful [Q2A functions]({{ site.baseurl }}/code/functions/) and have a browse around the Q2A source code to learn about many more. Finally, take a look at the code of the [many plugins]({{ site.baseurl }}/addons/) already developed for Q2A. Thank you and good luck! diff --git a/services.md b/services.md index cb5ba2a..07f791c 100644 --- a/services.md +++ b/services.md @@ -1,6 +1,7 @@ --- layout: page title: "Question2Answer - Services" +slug: singlepage-services --- # Question2Answer service providers diff --git a/themes/index.md b/themes/index.md index f95a544..175e006 100644 --- a/themes/index.md +++ b/themes/index.md @@ -1,6 +1,7 @@ --- layout: page title: "Question2Answer - Developers - Themes" +slug: themes --- # Creating themes for Question2Answer @@ -31,7 +32,7 @@ Question2Answer supports multiple themes, and makes it easy for you to create yo Some of this metadata will be displayed next to the theme setting in the Q2A admin interface. In addition, `Theme Update Check URI` allows you to inform users about new versions of the theme. Q2A will retrieve the content from `Theme Update Check URI`, and look for metadata in the same format as above. If the `Theme Version` values don't match, a message will show up, along with a link to the `Theme URI` from the online metadata. The simplest way to use this mechanism is to keep the latest version of `qa-styles.css` at a particular location online, and set that location as the `Theme Update Check URI`. -6. Please consider offering your theme online as a .zip file for others to download, then [telling us](http://www.question2answer.org/feedback.php) so we can [link to it here](/addons/). +6. Please consider offering your theme online as a .zip file for others to download, then [telling us](http://www.question2answer.org/feedback.php) so we can [link to it here]({{ site.baseurl }}/addons/). ## Creating an advanced theme for Question2Answer diff --git a/translate/index.md b/translate/index.md index 93102ba..dedbfc4 100644 --- a/translate/index.md +++ b/translate/index.md @@ -1,11 +1,12 @@ --- layout: page title: "Question2Answer - Developers - Translation / Localization" +slug: translate --- # Translating Question2Answer into another language -Question2Answer fully supports non-English languages and is easy to translate. Before proceeding, it's worth [checking here](/addons/translations/) to see if a translation is already available. You may be able to use this immediately, or only have to add some missing phrases. +Question2Answer fully supports non-English languages and is easy to translate. Before proceeding, it's worth [checking here]({{ site.baseurl }}/addons/translations/) to see if a translation is already available. You may be able to use this immediately, or only have to add some missing phrases. ## Creating a new translation @@ -31,7 +32,7 @@ To create a new Question2Answer translation from scratch, follow the steps below 7. **`qa-check-lang.php` is your friend!** As you work, use your web browser to view the `qa-include/qa-check-lang.php` page within your Question2Answer site. This will check your language files for any omitted phrases or substitutions. -8. Please consider sending us your translation or making it available online, so that we can [link to it here](/addons/translations/). +8. Please consider sending us your translation or making it available online, so that we can [link to it here]({{ site.baseurl }}/addons/translations/). ## Updating an existing translation @@ -52,7 +53,7 @@ An incomplete Question2Answer translation may be available for your language, fo 5. As you work, refresh `qa-include/qa-check-lang.php` in your web browser and ensure that you resolve all issues shown. You can also browse around your Q2A site to see how the translated phrases are used in context. -6. Please consider sending us your translation or making it available online, so that we can [link to it here](/addons/translations/). +6. Please consider sending us your translation or making it available online, so that we can [link to it here]({{ site.baseurl }}/addons/translations/). ## Customizing selected URLs or phrases From 85e71a7fb559c61a58ba3d745cfc4cb9cb792cce Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Mon, 18 Nov 2024 05:06:26 +0000 Subject: [PATCH 04/29] Check Homepage Popup early --- _layouts/home.html | 9 +++++++++ js/scripts.js | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/_layouts/home.html b/_layouts/home.html index 20c9cd6..e5d879a 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -17,6 +17,15 @@

    Question2Answer (Q2A) is a popular open
    +
    diff --git a/js/scripts.js b/js/scripts.js index 35695a9..820c21d 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -155,11 +155,6 @@ const handleClick = e => { } // End handleClick() document.addEventListener('click', handleClick); -// Deal with Notice saved state -const q2adocsNotice = localStorage.getItem('q2adocs_notice'); -if (localStorage.q2adocsNotice && noticeContainer != null) { - noticeContainer.classList.add('display-none'); -} // Quick fix to close Mega Menu, when clicking secondary nav items document.querySelectorAll('.nav-main-second .toggleChildren').forEach(element => { From 8dfd5f0726fed482425ce8033221e3adf4fafe62 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:09:22 +0000 Subject: [PATCH 05/29] Mobile Responsiveness --- css/styles.css | 11 +++++++++-- js/scripts.js | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/css/styles.css b/css/styles.css index b2872df..bf5f34d 100644 --- a/css/styles.css +++ b/css/styles.css @@ -498,7 +498,7 @@ ul.sub-nav { background-color: #ffffff; width: 70%; max-width: 90vw; - height: 80vh; + height: 70vh; overflow: hidden; border-radius: 12px; } @@ -1093,7 +1093,7 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { } .notice-text { padding: 1rem; - max-height: calc(80vh - 80px); + max-height: calc(70vh - 80px - 1rem); } .notice-image { height: 100px; @@ -1120,6 +1120,13 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { padding: .6rem 1rem; } } +@media (max-width: 375px) { + .twbb { + display: block; + font-size: 12px; + margin-right: .5rem; + } +} /* ==================== diff --git a/js/scripts.js b/js/scripts.js index 820c21d..d9fc866 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -363,7 +363,10 @@ if(gitLinks != null && gitLinks.length) { setTimeout(function(){ document.querySelector('.page-status-container').innerHTML = '\
    \ - There has been an update to this page. Please reload.\ +
    \ + There has been an update to this page.\ + Please reload.\ +
    \ refresh\
    \ '; From de04b8f23917effc49c54b102a33bea82e702642 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:17:25 +0000 Subject: [PATCH 06/29] Mobile Responsiveness --- css/styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/styles.css b/css/styles.css index bf5f34d..c120fdf 100644 --- a/css/styles.css +++ b/css/styles.css @@ -785,6 +785,7 @@ ul.sub-nav { margin: 0 auto; text-align: center; font-size: 1.5rem; + line-height: initial; font-weight: normal; } From e007af8d79e1ee62069bcc31538968aec95f045e Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Tue, 19 Nov 2024 01:45:46 +0000 Subject: [PATCH 07/29] Fix for null JS .selected-nav --- js/scripts.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/scripts.js b/js/scripts.js index d9fc866..5159e9d 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -64,7 +64,10 @@ if (document.querySelectorAll('.nav-main .selected-nav').length > 0) { document.querySelector('.mega-menu-trigger .nav-item').classList.add('selected-nav'); } // Add selected nav to the top level parent (second nav) -if (document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && !document.body.classList.contains('template-contribute')) { +if ( + document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && + !document.body.classList.contains('template-contribute') && + !document.body.classList.contains('template-addons')) { document.querySelector('.nav-main-second .selected-nav').closest('.sub-nav').previousElementSibling.classList.add('selected-nav'); } From f175570982dd80a14d0fd42e4ea023070d27b581 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:09:04 +0000 Subject: [PATCH 08/29] UX --- js/scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/scripts.js b/js/scripts.js index 5159e9d..44a16a5 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -373,7 +373,7 @@ if(gitLinks != null && gitLinks.length) { refresh\ \ '; - }, 2000); + }, 1500); } // Get saved data from LocalStorage From f3ec22257b27a648fbaf0c080650899b86f67546 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Tue, 19 Nov 2024 22:54:32 +0000 Subject: [PATCH 09/29] Homepage Notice --- _layouts/home.html | 11 +---------- js/scripts.js | 6 +++++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/_layouts/home.html b/_layouts/home.html index e5d879a..1197ee7 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -6,7 +6,7 @@

    Question2Answer (Q2A) is a popular open source Q&A platform for PHP/MySQL, currently running on 24,500+ sites in 40 languages.

    -
    + -
    diff --git a/js/scripts.js b/js/scripts.js index 44a16a5..6d761bb 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -152,12 +152,16 @@ const handleClick = e => { if (e.target.className.includes('close-notice') || e.target.className.includes('close-sheet')) { noticeContainer.classList.add('display-none'); // set Notice localStorage - localStorage.q2adocsNotice = 'closed'; + localStorage.q2adocs_notice = 'closed'; } } // End handleClick() document.addEventListener('click', handleClick); +// Show / Hide Notice on the front page +if (localStorage.getItem('q2adocs_notice') === null && noticeContainer != null) { + noticeContainer.classList.remove('display-none'); +} // Quick fix to close Mega Menu, when clicking secondary nav items document.querySelectorAll('.nav-main-second .toggleChildren').forEach(element => { From 2b6e4ed259c469dffda71d16e830d9c2dab45298 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:03:07 +0000 Subject: [PATCH 10/29] Repository Links UX color swap Repository Links, UX color swap suggestion by Gideon --- addons/plugins.md | 3 ++- addons/themes.md | 3 ++- css/styles.css | 14 +++++++++----- js/scripts.js | 6 ++++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/addons/plugins.md b/addons/plugins.md index 7beff74..e874478 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -9,7 +9,8 @@ slug: addons-plugins To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `qa-plugin` directory, then open the Plugins section of the Admin panel and check it is listed. The plugin may also offer some options on this page. -> 🟢 Status: up-to-date | 🔵 Status: Likely up-to-date | 🟡 Status: Hasn't been updated in a while | 🔘 Status: Inactive for a while | 🔴 Status: Unknown. +> Status description: +> 🟢 Up-to-date | 🔵 Likely up-to-date | 🟡 Hasn't been updated in a while | 🔴 Inactive for a while | 🔘 Unknown. > Note that "*Inactive*" & "*Unknown*" plugins doesn't necessarily mean that they do not work. Some plugins just don't require to be updated as frequently. ## Major features diff --git a/addons/themes.md b/addons/themes.md index 21dac4f..2c5464a 100644 --- a/addons/themes.md +++ b/addons/themes.md @@ -9,7 +9,8 @@ slug: addons-themes To install a theme, place its directory in Q2A's `qa-theme` directory, then open the General section of the Admin panel and choose the theme from the menu provided. **Many good themes can be found at [Q2A service providers](/services/)**. Below is a list of other recently updated themes: -> 🟢 Status: up-to-date | 🔵 Status: Likely up-to-date | 🟡 Status: Hasn't been updated in a while | 🔘 Status: Inactive for a while | 🔴 Status: Unknown. +> Status description: +> 🟢 Up-to-date | 🔵 Likely up-to-date | 🟡 Hasn't been updated in a while | 🔴 Inactive for a while | 🔘 Unknown. > Note that "*Inactive*" & "*Unknown*" themes doesn't necessarily mean that they do not work. Some themes just don't require to be updated as frequently. ## Free Themes diff --git a/css/styles.css b/css/styles.css index c120fdf..3d7d7bb 100644 --- a/css/styles.css +++ b/css/styles.css @@ -637,16 +637,20 @@ ul.sub-nav { background-color: #3498db; /* Blue */ color: #ffffff; } -.rep-date-3 { - background-color: #ffeb3b; /* Yellow */ +.rep-date-3, .rep-date-4 { + background-color: #ffe252; /* Yellow */ } -.rep-date-4, .rep-date-5 { - background-color: #e0e0e0; /* Grey */ +.rep-date-5 { + /* background-color: #e0e0e0; */ /* Grey */ + background-color: #e74c3c; + color: #ffffff; } +/* .rep-date-bad { - background-color: #e74c3c; /* Red */ + background-color: #e74c3c; color: #ffffff; } +*/ .rep-date + .rep-date { display: none; diff --git a/js/scripts.js b/js/scripts.js index 6d761bb..c466f14 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -1,6 +1,6 @@ // Days until next fetch for Github repository links -const daysUntilNextFetch = 90; // 3 months +const daysUntilNextFetch = 7; class githubList { constructor() { @@ -287,8 +287,10 @@ const calcYears = param => { yearGap = 2; } else if ( diffYears > 730 && diffYears <= 1095) { yearGap = 3; - } else if ( diffYears > 1095) { + } else if ( diffYears > 1095 && diffYears <= 1460) { yearGap = 4; + } else if ( diffYears > 1460) { + yearGap = 5; } return yearGap; } From 466faece3c12681dd6ec242fcc5d61b69b728b49 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:12:31 +0000 Subject: [PATCH 11/29] Refactored 'last updated' function, to check for 'max_q2a' key first --- js/scripts.js | 133 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 103 insertions(+), 30 deletions(-) diff --git a/js/scripts.js b/js/scripts.js index c466f14..a77fc6e 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -8,12 +8,22 @@ class githubList { this.themeList = []; } - addListLink(listType, index, linkInfo, dateInfo) { + addListLink(listType, index, linkInfo, dateInfo, q2aVersion) { // Push object to array, that later will be saved locally if(listType === 'plugins') { - this.pluginList.push({id: index.toString(), link: linkInfo.toString(), date: dateInfo.toString()}); + this.pluginList.push({ + id: index.toString(), + link: linkInfo.toString(), + date: dateInfo.toString(), + max_q2a: q2aVersion.toString() + }); } else { - this.themeList.push({id: index.toString(), link: linkInfo.toString(), date: dateInfo.toString()}); + this.themeList.push({ + id: index.toString(), + link: linkInfo.toString(), + date: dateInfo.toString(), + max_q2a: q2aVersion.toString() + }); } } @@ -295,6 +305,27 @@ const calcYears = param => { return yearGap; } +const calcVersion = (pluginVersion, q2aVersion) => { + pluginVersion = pluginVersion.split('.').join(''); + q2aVersion = q2aVersion.split('.').join(''); + + let versionGap = 0; + const calcVersionGap = (q2aVersion - pluginVersion); + + if ( calcVersionGap === 1) { + versionGap = 1; + } else if ( calcVersionGap === 2) { + versionGap = 2; + } else if ( calcVersionGap === 3) { + versionGap = 3; + } else if ( calcVersionGap === 4) { + versionGap = 4; + } else if ( calcVersionGap >= 5) { + versionGap = 5; + } + return versionGap; +} + // Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) const gitLinks = document.querySelectorAll('\ .template-addons-plugins .page-content li a[href*="https://github.com/"],\ @@ -317,15 +348,34 @@ if(gitLinks != null && gitLinks.length) { // Fetch Links const fetchLinks = () => { + // Get Q2A version + let q2aVersion; + const getQ2aVersion = 'https://raw.githubusercontent.com/q2a/question2answer/master/VERSION.txt'; + let rawFile = new XMLHttpRequest(); + rawFile.open('GET', getQ2aVersion, false); + rawFile.onreadystatechange = function () + { + if(rawFile.readyState === 4) + { + if(rawFile.status === 200 || rawFile.status == 0) + { + q2aVersion = rawFile.responseText; + console.log(allText); + } + } + } + rawFile.send(null); + // Set list headers if(isPluginsPage) { - pluginsList.addListLink('plugins', '1000', 'List_length', pluginLinks.length); - pluginsList.addListLink('plugins', '1001', 'updated', new Date()); + pluginsList.addListLink('plugins', '1000', 'List_length', pluginLinks.length, q2aVersion); + pluginsList.addListLink('plugins', '1001', 'updated', new Date(), q2aVersion); } else if (isThemesPage) { - themesList.addListLink('themes', '1000', 'List_length', themeLinks.length); - themesList.addListLink('themes', '1001', 'updated', new Date()); + themesList.addListLink('themes', '1000', 'List_length', themeLinks.length, q2aVersion); + themesList.addListLink('themes', '1001', 'updated', new Date(), q2aVersion); } + // Create list for(let i=0; i { if(isRepository) { // Add link to list + const getQ2aVersion = (jsonResponse.max_q2a != null) ? jsonResponse.max_q2a : '0'; + if(isPluginsPage) { - pluginsList.addListLink('plugins', [i], gitLinks[i], jsonResponse.date); + pluginsList.addListLink('plugins', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); } else if (isThemesPage) { - themesList.addListLink('themes', [i], gitLinks[i], jsonResponse.date); + themesList.addListLink('themes', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); } } }) @@ -353,9 +405,9 @@ if(gitLinks != null && gitLinks.length) { // We can remove the display tag in the createPluginTags() functions instead, by removing the "else" statement. if(isRepository) { if(isPluginsPage) { - pluginsList.addListLink('plugins', [i], gitLinks[i], 'unknown'); + pluginsList.addListLink('plugins', [i], gitLinks[i], 'unknown', '0'); } else if (isThemesPage) { - themesList.addListLink('themes', [i], gitLinks[i], 'unknown'); + themesList.addListLink('themes', [i], gitLinks[i], 'unknown', '0'); } } }) @@ -396,38 +448,58 @@ if(gitLinks != null && gitLinks.length) { // Create tags for both - Plugins and Themes const createTags = (param) => { + + // get stored current Q2A version + const currentQ2aVersion = Object.values(param[0] || {} )[3]; + param.forEach((item, index) => { - - const test = param.slice(2); // Remove list header (metadata) indexes - const id = Object.values(test[index] || {} )[0]; - const link = Object.values(test[index] || {} )[1]; - const date = Object.values(test[index] || {} )[2]; + + const list = param.slice(2); // Remove list header (metadata) indexes + const id = Object.values(list[index] || {} )[0]; + const link = Object.values(list[index] || {} )[1]; + const date = Object.values(list[index] || {} )[2]; + const max_q2a = Object.values(list[index] || {} )[3]; // console.log(`${id} === ${link} === ${date}`); - if(id != null && link != null && date != null) { + if(id != null && link != null && date != null && max_q2a != null) { // Preppend based on stored id/index, because DOM link order may not be accurate when looping - // index order will be updated when information is stored/fetched again - if(date != 'unknown') { - const yearGapClass = 'rep-date-' + calcYears(date); - gitLinks[id].insertAdjacentHTML( - 'beforebegin', - `${date}`, - ); - } else if(date == 'unknown') { - // If response is 'bad request' or '404', show "unknown" tag - gitLinks[id].insertAdjacentHTML( - 'beforebegin', - `Unknown`, - ); + // index order will be updated when information is fetched again + + // if 'max_q2a' key was available at the time of fetching + if(max_q2a != '0') { + if(date != 'unknown') { // prevention of worst case, 'max_q2a' available but not 'date'. + const versionGapClass = 'rep-date-' + calcVersion(max_q2a, currentQ2aVersion); + gitLinks[id].insertAdjacentHTML( + 'beforebegin', + `${date}`, + ); + } + } else { + // else determine badge based on date + if(date != 'unknown') { + const yearGapClass = 'rep-date-' + calcYears(date); + gitLinks[id].insertAdjacentHTML( + 'beforebegin', + `${date}`, + ); + } else if(date == 'unknown') { + // If response is 'bad request' or '404', show "unknown" tag + gitLinks[id].insertAdjacentHTML( + 'beforebegin', + `Unknown`, + ); + } } } }); + // console.log(JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins'))); } // Start at zero, in case not fetched yet let pluginsListUpdated = currentDate(); let pluginListLength = 0; + let themesListUpdated = currentDate(); let themeListLength = 0; @@ -443,6 +515,7 @@ if(gitLinks != null && gitLinks.length) { themesListUpdated = new Date(themesListUpdated).toISOString().split('T')[0]; themeListLength = singleValue(retrievedThemes[0])[2]; } + // ---------------------------- // Create the tags / badges --- // ---------------------------- From 0006806cb466e48d36d58e4dcc5b78b8beae2284 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:34:26 +0000 Subject: [PATCH 12/29] Tested with Q2A --- css/styles.css | 2 ++ js/scripts.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/css/styles.css b/css/styles.css index 3d7d7bb..9ac6cba 100644 --- a/css/styles.css +++ b/css/styles.css @@ -627,6 +627,8 @@ ul.sub-nav { margin-inline-end: 6px; margin-bottom: 6px; cursor: help; + font-size: 88%; + vertical-align: top; } .rep-date-0, .rep-date-1 { diff --git a/js/scripts.js b/js/scripts.js index a77fc6e..af68f3d 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -472,7 +472,7 @@ if(gitLinks != null && gitLinks.length) { const versionGapClass = 'rep-date-' + calcVersion(max_q2a, currentQ2aVersion); gitLinks[id].insertAdjacentHTML( 'beforebegin', - `${date}`, + `Tested with Q2A ${max_q2a}`, ); } } else { @@ -481,7 +481,7 @@ if(gitLinks != null && gitLinks.length) { const yearGapClass = 'rep-date-' + calcYears(date); gitLinks[id].insertAdjacentHTML( 'beforebegin', - `${date}`, + `Updated ${date}`, ); } else if(date == 'unknown') { // If response is 'bad request' or '404', show "unknown" tag From ef06a842eaaa47db5126dc4c3d2538a69fb8095a Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:05:13 +0000 Subject: [PATCH 13/29] Tested with version N --- addons/plugins.md | 52 ++++++++++++++++-------- addons/themes.md | 2 +- css/styles.css | 76 ++++++++++++++++++++++++++++------ images/q2a-icon.svg | 1 + js/scripts.js | 99 ++++++++++++++++++++++++++------------------- 5 files changed, 158 insertions(+), 72 deletions(-) create mode 100644 images/q2a-icon.svg diff --git a/addons/plugins.md b/addons/plugins.md index e874478..4bfdc69 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -11,11 +11,12 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q > Status description: > 🟢 Up-to-date | 🔵 Likely up-to-date | 🟡 Hasn't been updated in a while | 🔴 Inactive for a while | 🔘 Unknown. -> Note that "*Inactive*" & "*Unknown*" plugins doesn't necessarily mean that they do not work. Some plugins just don't require to be updated as frequently. +> Note that "*Inactive*" & "*Unknown*" plugins doesn't necessarily mean that they do not work. Some plugins just don't require to be updated as frequently. Look for the "Q2A Tested" tag as well. ## Major features -- [Badges Plugin](https://github.com/NoahY/q2a-badges) by [NoahY](http://www.question2answer.org/qa/user/NoahY) ➔ [updated](https://github.com/rxchun/q2a-badges/) by [Chun](https://rxchun.github.io/). Provides user badges which recognize users' activities and achievements. +- [Badges Plugin](https://github.com/NoahY/q2a-badges) by [NoahY](http://www.question2answer.org/qa/user/NoahY) Provides user badges which recognize users' activities and achievements. +- ➔ [Badges Plugin updated](https://github.com/rxchun/q2a-badges/) by [Chun](https://rxchun.github.io/). - [Better Points](https://github.com/arjunsuresh/q2a-betterpoints) by [Arjun](http://gateoverflow.in/user/Arjun). Extends the default Q2A point system by adding points for giving comments, receiving comment upvotes/downvotes. Also, has an option to give points for blog/exam postings which are supported by paid plugins. - [Category Experts](https://github.com/arjunsuresh/q2a-categoryexperts) by [Arjun](http://gateoverflow.in/user/Arjun). Adds a widget on Category pages showing the best performing users in that category. A good way to identify Category Experts. - [Extra Question Field](https://github.com/JacksiroKe/q2a-extra-question-field) by [Jack Siro](https://www.linkedin.com/in/jacksiroke/). Enhance your Extra Question fields with file management among other extra fields. @@ -28,15 +29,21 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Embed Youtube Plugin](https://github.com/NoahY/q2a-embed) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Converts links to YouTube videos into embedded videos when displaying questions, answers or comments. - [Express Editor](https://github.com/amiyasahu/q2a-express-editor) by [Amiya Sahu](http://amiyasahu.com/). Recent version of [CKEditor](http://ckeditor.com/) bundled with [Ace Editor](http://ace.c9.io/) for smooth code editing. - [Lightbox-images](https://github.com/ProThoughts/q2apro-lightbox-images) by [q2apro.com](https://github.com/q2apro). Provides a Lightbox Effect for all images in posts. -- [Markdown Editor Plugin](https://github.com/ProThoughts/markdown-editor-for-q2a) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/), ➔ updated for Q2A 1.8.6 [here](https://github.com/fardina/markdown-editor-for-q2a). +- [Markdown Editor Plugin](https://github.com/ProThoughts/markdown-editor-for-q2a) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). +- ➔ [Markdown Editor Plugin Updated](https://github.com/fardina/markdown-editor-for-q2a) updated for Q2A 1.8.6 - [Memes for Text](https://github.com/thibaultduponchelle/q2a-memes) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). Post *lolfaces* in your answers or comments. - [Miranda](http://www.question2answer.org/qa/51849/tinymcewrapper-miranda-updated-most-powerful-editor-joins) [Tinymcse Wrapper](https://www.tinymce.com/) by Don Shakespeare. Adds Tinymcse to be used across posts as well as other text areas like messages, wall posts and is higly customizable. -- [Prevent Simultaneous Edits](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [q2apro.com](https://github.com/q2apro). Prevent two users editing the same post simultaneously. Also [forked](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [Jalal Jaberi](https://github.com/ElephantsGroup). +- [Prevent Simultaneous Edits](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [q2apro.com](https://github.com/q2apro). Prevent two users editing the same post simultaneously. +- ➔ [Prevent Simultaneous Edits also forked](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [Jalal Jaberi](https://github.com/ElephantsGroup). - [SCEditor Plugin](https://github.com/q2apro/SCEditor) by [q2apro.com](https://github.com/q2apro). Provides [SCEditor](http://www.sceditor.com) as a WYSIWYG rich text editor. Some functionality requires the Premium version. - [Shortcode Plugin](https://github.com/NoahY/q2a-shortcode) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows posts to be displayed with text substitutions, including support for regular expressions. -- [Smilies for Text](https://github.com/NoahY/q2a-smilies) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows smilies to be embedded in plain text or Markdown content. Also [forked](https://github.com/thibaultduponchelle/q2a-smilies) by [Thibault Duponchelle](https://github.com/thibaultduponchelle) to support multiboxes, add a lot of smilies and slightly improve the style. +- [Smilies for Text](https://github.com/NoahY/q2a-smilies) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows smilies to be embedded in plain text or Markdown content. +- ➔ [Smilies for Text also forked](https://github.com/thibaultduponchelle/q2a-smilies) by [Thibault Duponchelle](https://github.com/thibaultduponchelle) to support multiboxes, add a lot of smilies and slightly improve the style. - [Syntax Highlighter](https://github.com/amiyasahu/q2a-syntax-highlighter) by [Amiya Sahu](http://amiyasahu.com/). Code syntax highlighting based on [highlight.js](https://highlightjs.org/) with over 40 built-in themes. -- [WYSIWYG Math Editor](https://github.com/thibaultduponchelle/q2a-wysiwyg-matheditor) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). A CKEditor 4 editor plugin *with math capabilities*. Allow users to add math formulas (MathJax) in questions, answers and comments. Other math editors based on *preview zone* like [SCEditor](https://github.com/ProThoughts/q2apro-sceditor-free) were already mentioned above. Also: [q2a-formatter](https://github.com/tangruize/q2a-formatter) (or [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) from [Arjun Suresh](https://github.com/arjunsuresh) from which [q2a-formatter](https://github.com/tangruize/q2a-formatter) was forked). +- [WYSIWYG Math Editor](https://github.com/thibaultduponchelle/q2a-wysiwyg-matheditor) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). A CKEditor 4 editor plugin *with math capabilities*. Allow users to add math formulas (MathJax) in questions, answers and comments. +- ➔ Other math editors based on *preview zone* like [SCEditor](https://github.com/ProThoughts/q2apro-sceditor-free) were already mentioned above. +- ➔ Also: [q2a-formatter](https://github.com/tangruize/q2a-formatter) by [tangruize](https://github.com/tangruizer). +- ➔ Or [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) from [Arjun Suresh](https://github.com/arjunsuresh) from which **q2a-formatter** was forked. - [Paid] [DM - Dynamic Mentions](https://www.question2answer.org/qa/66929) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows users to be mentioned using the @user syntax. @@ -74,13 +81,17 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q ## Additional page types -- [Best Users per Month](https://github.com/ProThoughts/q2apro-best-users-pro) by [q2apro.com](https://github.com/q2apro). Displays the users with the most points each month (for large sites there is a premium plugin which does not have performance issue). Also [forked](https://github.com/ElephantsGroup/q2a-best-users-per-month) by [Jalal Jaberi](https://github.com/ElephantsGroup) which supports Jalali calendar. +- [Best Users per Month](https://github.com/ProThoughts/q2apro-best-users-pro) by [q2apro.com](https://github.com/q2apro). Displays the users with the most points each month (for large sites there is a premium plugin which does not have performance issue). +- ➔ Also [forked](https://github.com/ElephantsGroup/q2a-best-users-per-month) by [Jalal Jaberi](https://github.com/ElephantsGroup) which supports Jalali calendar. - [Blog Post](https://github.com/JaxiroKe/q2a-blog-post) by [Jack Siro](https://www.linkedin.com/in/JaxiroKe/). Allows registered users to maintain a blog on their Q2A site. -- [Book Plugin](https://github.com/NoahY/q2a-book) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Creates an HTML (or PDF) book of a site's top questions and answers. Also [forked](https://github.com/arjunsuresh/q2a-book) by [Arjun](http://gateoverflow.in/user/Arjun) which adds more question filtering options and better styling options. +- [Book Plugin](https://github.com/NoahY/q2a-book) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Creates an HTML (or PDF) book of a site's top questions and answers. +- ➔ Also [forked](https://github.com/arjunsuresh/q2a-book) by [Arjun](http://gateoverflow.in/user/Arjun) which adds more question filtering options and better styling options. - [Chat Room](https://github.com/svivian/q2a-chat-room) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). Adds a simple chat room to a Q2A site. - [Custom 404 Page](https://github.com/amiyasahu/q2a-custom-404-page) by [Amiya Sahu](http://amiyasahu.com/). Allows the content of the 404 (page not found) page to be customized. -- [Edit History](https://github.com/svivian/q2a-edit-history) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). Stores all edits to posts and allows users to see what changed. Also [forked](https://github.com/ElephantsGroup/q2a-edit-history) by [Jalal Jaberi](https://github.com/ElephantsGroup). -- [FAQ Page](https://github.com/gturri/q2a-faq/) forked from [older version](https://github.com/NoahY/q2a-faq) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Provides a customizable FAQs page which can include an explanation of the points system on your Q2A site. +- [Edit History](https://github.com/svivian/q2a-edit-history) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). Stores all edits to posts and allows users to see what changed. +- ➔ Also [forked](https://github.com/ElephantsGroup/q2a-edit-history) by [Jalal Jaberi](https://github.com/ElephantsGroup). +- [FAQ Page](https://github.com/NoahY/q2a-faq) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Provides a customizable FAQs page which can include an explanation of the points system on your Q2A site. +- ➔ [FAQ Page forked](https://github.com/gturri/q2a-faq/) from NoahY's older version by [gturri](https://github.com/gturri). - [Open Questions Plugin](https://github.com/nakov/q2a-plugin-open-questions) by [Svetlin Nakov](http://www.nakov.com/). Provides a page displaying all questions with no answers and which have not been closed. - [Polls Plugin](https://github.com/NoahY/q2a-poll) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows some questions to be created as polls. - [Print View Plugin](https://github.com/NoahY/q2a-print) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds a simplified print view for questions. @@ -99,7 +110,8 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Feed Widget](https://github.com/Towhidn/Q2A-feed-widget) by [Towhid](http://TowhidN.com/). Lists recent items from an external RSS feed. Useful for blog posts, articles, etc... - [Logarithmic Tag Cloud](https://github.com/NoahY/q2a-log-tags) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Provides a list of tags with logarithmic size indicating popularity. - [Most Active Users](https://github.com/yshiga/q2apro-most-active-users) by [q2apro.com](https://github.com/q2apro). Shows a list of users who were most active in the past week or month. -- [Online User Count](https://github.com/pupi1985/q2a-onlineusers/tree/a1e555b77b5a0585f8c336d9e37e1e4a2bece437) by Ali Sayahiyan. Shows a list of users who are currently online as well as total visitors. Also [forked here](https://github.com/pupi1985/q2a-onlineusers) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). +- [Online User Count](https://github.com/pupi1985/q2a-onlineusers/tree/a1e555b77b5a0585f8c336d9e37e1e4a2bece437) by Ali Sayahiyan. Shows a list of users who are currently online as well as total visitors. +- ➔ Also [forked here](https://github.com/pupi1985/q2a-onlineusers) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). - [Popular Questions](https://github.com/ProThoughts/q2apro-popular-questions-widget) by [q2apro.com](https://github.com/q2apro). Shows a list of the most viewed questions. - [Random Question Widget](https://github.com/NoahY/qa-random-question) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Displays a random question in the sidebar. - [Recent Events Widget](https://github.com/fullstack412/Q2A-Recent-Events-Widget) by [q2apro.com](https://github.com/q2apro). Displays all recent events in the sidebar or main area. @@ -111,7 +123,8 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Tag List Widget](https://github.com/svivian/q2a-tag-list-widget) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). Displays the most popular tags in a simple list. - [Twitter Widget](https://github.com/Towhidn/q2a-twitter) by [Towhid](http://TowhidN.com/). Shows recent tweets from your twitter account. - [Widget Anywhere Plugin](https://github.com/svivian/q2a-widget-anywhere) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). Allows custom content to be added on any page, in a variety of locations. -- [Simple Ads Manager](https://github.com/ProThoughts/q2a-simple-ads-manager) by [ProThoughts](http://ProThoughts.com). Allow you to add advertisements in listed spots. You can use Google adsense or any HTML ad code. Also [forked](https://github.com/arjunsuresh/q2a-simple-ads-manager) by [Arjun](http://gateoverflow.in/user/Arjun) to add more options like Ads after first question hiding ads for certain user level and above etc. +- [Simple Ads Manager](https://github.com/ProThoughts/q2a-simple-ads-manager) by [ProThoughts](http://ProThoughts.com). Allow you to add advertisements in listed spots. You can use Google adsense or any HTML ad code. +- ➔ Also [forked](https://github.com/arjunsuresh/q2a-simple-ads-manager) by [Arjun](http://gateoverflow.in/user/Arjun) to add more options like Ads after first question hiding ads for certain user level and above etc. - [Paid] [AS - Advanced Search](https://www.question2answer.org/qa/54072) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Extends the way in which Q2A searches for posts and allows the user to get better results when searching. ## User interface @@ -138,14 +151,17 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Question Filter](https://github.com/ganbox/qa-filter) by Georgi Stefanov @ [Ganbox](http://ganbox.com/). Adds filtering of user input when asking questions, to check duplication, capitalization, etc... - [Question Numbering](https://github.com/arjunsuresh/q2a-qnumbering) by [Arjun](http://gateoverflow.in/user/Arjun). Adds numbering to questions in question listing pages. - [Quick-login](https://github.com/ProThoughts/q2apro-quick-login) by [q2apro.com](https://github.com/q2apro). Provides a quick login field for all your users. -- [Role Markers](https://github.com/NoahY/q2a-role-markers) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds markers to the avatars displayed for users with special privileges. Also [forked](https://github.com/zakkak/q2a-role-markers) by [Zakkak](https://github.com/zakkak). +- [Role Markers](https://github.com/NoahY/q2a-role-markers) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds markers to the avatars displayed for users with special privileges. +- ➔ Also [forked](https://github.com/zakkak/q2a-role-markers) by [Zakkak](https://github.com/zakkak). - [Social Sharing Plugin](https://github.com/NoahY/q2a-share) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds social sharing buttons to question pages. - [Sort Answers](https://github.com/amiyasahu/q2a-sort-answers) by [Amiya Sahu](http://amiyasahu.com/). Allows answers on a question page to be sorted by oldest, newest and highest voted. - [Tag Search](https://github.com/arjunsuresh/tag-search) by [Arjun](http://gateoverflow.in/user/Arjun). Allows tags to be searched via AJAX and with auto complete. - [Tag Descriptions Plugin](http://www.question2answer.org/releases/q2a-tag-descriptions-tutorial.zip) from the [plugin tutorial]({{ site.baseurl }}/plugins/tutorial/). Allows tag descriptions to be shown on tag pages and in tooltips. - [Theme Switcher Plugin](https://github.com/arjunsuresh/q2a-theme-switch) by [NoahY](http://www.question2answer.org/qa/user/NoahY) and updated by [Arjun](http://gateoverflow.in/user/Arjun). Allows users to choose their own theme on their profile page for normal as well as mobile view. -- [Top Searches](https://github.com/arjunsuresh/q2a-top-search) by [Arjun](http://gateoverflow.in/user/Arjun). Adds a widget to display the top searched words. Also have an option to display top seacrhed tags if using [Tag Search](https://github.com/arjunsuresh/tag-search) plugin. -- [User Signatures Plugin](https://github.com/NoahY/q2a-signatures) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to define a signature for all of their posts. Also [forked here](https://github.com/pupi1985/q2a-signatures) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). +- [Top Searches](https://github.com/arjunsuresh/q2a-top-search) by [Arjun](http://gateoverflow.in/user/Arjun). Adds a widget to display the top searched words. +- ➔ Also have an option to display top searched tags if using [Tag Search](https://github.com/arjunsuresh/tag-search) plugin. +- [User Signatures Plugin](https://github.com/NoahY/q2a-signatures) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to define a signature for all of their posts. +- ➔ Also [forked here](https://github.com/pupi1985/q2a-signatures) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). - [Userinfo](https://github.com/ProThoughts/q2apro-userinfo) by [q2apro.com](https://github.com/q2apro). Mouse over a username to display user profile information: Avatar image, account age, total points, monthly points, answers, best answers, ratio, questions posted, badges. - [Userslist-locations](https://github.com/ProThoughts/q2apro-userslist-locations) by [q2apro.com](https://github.com/q2apro). Add Location to users list - [Warn on Leave](https://github.com/yshiga/q2apro-warn-on-leave) by [q2apro.com](https://github.com/q2apro). Warns users before leaving a page with text they have entered (also support WYSIWYG editor). @@ -182,7 +198,8 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q ## Administrative tools - [Admin Plus Plugin](https://github.com/NoahY/q2a-admin-plus) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Extends the admin area by allowing PHP commands to be executed. (Use with caution!) -- [Batch Import Users](https://github.com/ostack/q2a-user-import) updated and fixed by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146). [Original](https://github.com/thisliquidspace/q2a-user-import). Batch import users from CSV files. +- [Batch Import Users](https://github.com/thisliquidspace/q2a-user-import). Batch import users from CSV files. +- ➔ [Batch Import Users Updated](https://github.com/ostack/q2a-user-import) updated and fixed by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146). - [Categorization Plugin](https://github.com/NoahY/q2a-cat) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows individual categorization of uncategorized posts via the categories admin panel. - [Change Post Owner](https://github.com/ProThoughts/q2apro-change-post-owner) by [q2apro.com](https://github.com/q2apro). Allows the author of a post to be changed, or to make it anonymous. - [Convert Comment to Answer](https://github.com/ellcupakabra/q2apro-comment-to-answer) by [q2apro.com](https://github.com/q2apro). Convert a comment to an answer, optionally move the succeeding comments. @@ -203,7 +220,8 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Translator](https://github.com/pedjas/q2a-translator) by [Pedja Supurovic](http://pedja.supurovic.net). Allows translations to be created online through pages in the administration interface. - [Webmaster Tools](https://github.com/Towhidn/Q2A-Webmaster) by [Towhid](http://TowhidN.com). Reports include server info and memory usage, SEO analysis, security warnings. - [Quick-edit](https://github.com/ProThoughts/q2apro-quick-edit) by [q2apro.com](https://github.com/q2apro). Update all question titles and tags quickly on one page and save hours of time. -- [Ultimate SEO](https://github.com/q2a-projects/Q2A-Ultimate-SEO) by [Towhid](http://TowhidN.com). Enhances Question2Answer for better search engine optimization and offers valuable customization features. Also [forked here](https://github.com/pupi1985/q2a-ultimate-seo) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). +- [Ultimate SEO](https://github.com/q2a-projects/Q2A-Ultimate-SEO) by [Towhid](http://TowhidN.com). Enhances Question2Answer for better search engine optimization and offers valuable customization features. +- ➔ Also [forked here](https://github.com/pupi1985/q2a-ultimate-seo) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). - [Uploadmanager](https://github.com/ProThoughts/q2apro-uploadmanager) by [q2apro.com](https://github.com/q2apro). A complete upload manager for question2answer with upload details and image rotate features. - [Userstatistics](https://github.com/ProThoughts/q2apro-userstatistics) by [q2apro.com](https://github.com/q2apro). A complete user statistic with detailed points per post and thumbs and more. - [Votes-manager](https://github.com/ProThoughts/q2apro-votes-manager) by [q2apro.com](https://github.com/q2apro). Lists all votes made in your forum. diff --git a/addons/themes.md b/addons/themes.md index 2c5464a..fff711a 100644 --- a/addons/themes.md +++ b/addons/themes.md @@ -11,7 +11,7 @@ To install a theme, place its directory in Q2A's `qa-theme` directory, then open > Status description: > 🟢 Up-to-date | 🔵 Likely up-to-date | 🟡 Hasn't been updated in a while | 🔴 Inactive for a while | 🔘 Unknown. -> Note that "*Inactive*" & "*Unknown*" themes doesn't necessarily mean that they do not work. Some themes just don't require to be updated as frequently. +> Note that "*Inactive*" & "*Unknown*" themes doesn't necessarily mean that they do not work. Some themes just don't require to be updated as frequently. Look for the "Q2A Tested" tag as well. ## Free Themes diff --git a/css/styles.css b/css/styles.css index 9ac6cba..f131328 100644 --- a/css/styles.css +++ b/css/styles.css @@ -135,7 +135,8 @@ pre code { } blockquote { - background-color: #FFE0B2; + background-color: #fff9e1; + border-left: 3px solid #ffef95; padding: .1rem 1rem; margin: 0; } @@ -613,22 +614,76 @@ ul.sub-nav { border-radius: 0.5rem; } +#question2answer-plugins ~ ul, #question2answer-themes ~ ul { + list-style: none; + margin: 0; + padding: 0; +} + #question2answer-plugins ~ ul li, #question2answer-themes ~ ul li { border-bottom: 1px solid #e0e0e0; - margin: .3rem 0; + padding: .8rem 1rem; +} + +#question2answer-plugins ~ ul .child-repository, +#question2answer-themes ~ ul .child-repository { + padding-left: 2rem; +} + +.child-repository { + position: relative; +} + +.child-repository:before { + content: ""; + position: absolute; + top: .8rem; + left: 1rem; + bottom: .8rem; + width: 4px; + background-color: rgb(0 0 0 / 10%); +} + +#question2answer-plugins ~ ul li:hover, +#question2answer-themes ~ ul li:hover { + background-color: rgb(0 0 0 / 2%); +} + +#question2answer-plugins ~ ul li a:first-child, #question2answer-themes ~ ul li a:first-child { + font-weight: bold; +} + +.repository-footer { + display: flex; + margin-top: .5rem; + color: rgb(95, 99, 104); + font-size: 88%; +} + +.rf-item:first-child { + margin-right: 1rem; +} + +.rf-item ~ .rf-item { + margin-left: auto; +} + +.rf-item .docs-svg { + width: 35px; + height: auto; + vertical-align: middle; } .rep-date { display: inline-block; background-color: #e0e0e0; - padding: 0px 6px; - border-radius: 3px; - margin-inline-end: 6px; - margin-bottom: 6px; - cursor: help; - font-size: 88%; - vertical-align: top; + margin-inline-start: 4px; + margin-inline-end: 4px; + min-width: 18px; + min-height: 10px; + vertical-align: middle; + border-radius: 30px; } .rep-date-0, .rep-date-1 { @@ -654,9 +709,6 @@ ul.sub-nav { } */ -.rep-date + .rep-date { - display: none; -} .page-sidebar { width: 21.125rem; diff --git a/images/q2a-icon.svg b/images/q2a-icon.svg new file mode 100644 index 0000000..7fa84c4 --- /dev/null +++ b/images/q2a-icon.svg @@ -0,0 +1 @@ + diff --git a/js/scripts.js b/js/scripts.js index af68f3d..09ade92 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -305,27 +305,6 @@ const calcYears = param => { return yearGap; } -const calcVersion = (pluginVersion, q2aVersion) => { - pluginVersion = pluginVersion.split('.').join(''); - q2aVersion = q2aVersion.split('.').join(''); - - let versionGap = 0; - const calcVersionGap = (q2aVersion - pluginVersion); - - if ( calcVersionGap === 1) { - versionGap = 1; - } else if ( calcVersionGap === 2) { - versionGap = 2; - } else if ( calcVersionGap === 3) { - versionGap = 3; - } else if ( calcVersionGap === 4) { - versionGap = 4; - } else if ( calcVersionGap >= 5) { - versionGap = 5; - } - return versionGap; -} - // Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) const gitLinks = document.querySelectorAll('\ .template-addons-plugins .page-content li a[href*="https://github.com/"],\ @@ -452,6 +431,21 @@ if(gitLinks != null && gitLinks.length) { // get stored current Q2A version const currentQ2aVersion = Object.values(param[0] || {} )[3]; + // Change Emoji colors for actual color element + const colorLegends = document.querySelector('.template-addons-plugins blockquote, .template-addons-themes blockquote'); + const colorLegendsUpdated = colorLegends.innerHTML.replace( + '🟢','' + ).replace( + '🔵','' + ).replace( + '🟡','' + ).replace( + '🔴','' + ).replace( + '🔘','' + ); + colorLegends.innerHTML = colorLegendsUpdated; + param.forEach((item, index) => { const list = param.slice(2); // Remove list header (metadata) indexes @@ -462,35 +456,56 @@ if(gitLinks != null && gitLinks.length) { // console.log(`${id} === ${link} === ${date}`); + if(gitLinks[id].parentElement != null && gitLinks[id].parentElement.innerHTML.includes('➔')){ + gitLinks[id].closest('li').classList.add('child-repository'); + } + if(id != null && link != null && date != null && max_q2a != null) { // Preppend based on stored id/index, because DOM link order may not be accurate when looping // index order will be updated when information is fetched again - // if 'max_q2a' key was available at the time of fetching - if(max_q2a != '0') { - if(date != 'unknown') { // prevention of worst case, 'max_q2a' available but not 'date'. - const versionGapClass = 'rep-date-' + calcVersion(max_q2a, currentQ2aVersion); - gitLinks[id].insertAdjacentHTML( - 'beforebegin', - `Tested with Q2A ${max_q2a}`, - ); - } - } else { - // else determine badge based on date - if(date != 'unknown') { - const yearGapClass = 'rep-date-' + calcYears(date); - gitLinks[id].insertAdjacentHTML( - 'beforebegin', - `Updated ${date}`, + const yearGapClass = 'rep-date-' + calcYears(date); + + if (date != 'unknown') { + // if 'max_q2a' key is available + if(max_q2a != '0') { + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + ${date} + + + + Tested with ${max_q2a} + + `, ); - } else if(date == 'unknown') { - // If response is 'bad request' or '404', show "unknown" tag - gitLinks[id].insertAdjacentHTML( - 'beforebegin', - `Unknown`, + } else { + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + ${date} + + `, ); } + } else if (date == 'unknown') { + // If response is 'bad request' or '404', show "unknown" tag + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + Unknown + + `, + ); } + } }); // console.log(JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins'))); From 67a78172e70af7b663d0e256fe66820537323a39 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:17:35 +0000 Subject: [PATCH 14/29] Mobile Responsiveness --- css/styles.css | 12 +++++++++--- js/scripts.js | 6 ++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/css/styles.css b/css/styles.css index f131328..000912f 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1172,9 +1172,15 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { .page-content { padding: 0; } - #question2answer-plugins ~ ul, #question2answer-themes ~ ul { - padding-left: 1rem; - } + #question2answer-plugins ~ ul .child-repository, #question2answer-themes ~ ul .child-repository { + padding-left: 1.5rem; + } + .child-repository:before { + left: .5rem; + } + .rep-date-true { + white-space: nowrap; + } .page-status { padding: .6rem 1rem; } diff --git a/js/scripts.js b/js/scripts.js index 09ade92..e1a3062 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -474,7 +474,8 @@ if(gitLinks != null && gitLinks.length) { ` Last updated: - ${date} + + ${date} @@ -488,7 +489,8 @@ if(gitLinks != null && gitLinks.length) { ` Last updated: - ${date} + + ${date} `, ); From 5ccfc31761cf9a134b39523e09700c96cacfe916 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 09:21:46 +0000 Subject: [PATCH 15/29] Mobile Responsive --- css/styles.css | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/css/styles.css b/css/styles.css index 000912f..dd1e4f8 100644 --- a/css/styles.css +++ b/css/styles.css @@ -614,12 +614,23 @@ ul.sub-nav { border-radius: 0.5rem; } +.template-addons-plugins .sectionTitle { + margin-top: 3rem; +} +.template-addons-plugins .page-content blockquote ~ .sectionTitle { + margin-top: revert; +} + #question2answer-plugins ~ ul, #question2answer-themes ~ ul { list-style: none; margin: 0; padding: 0; } +#question2answer-plugins ~ ul:not(:nth-last-child(2)) { + margin-bottom: 3rem; +} + #question2answer-plugins ~ ul li, #question2answer-themes ~ ul li { border-bottom: 1px solid #e0e0e0; @@ -656,7 +667,7 @@ ul.sub-nav { .repository-footer { display: flex; - margin-top: .5rem; + margin-top: .2rem; color: rgb(95, 99, 104); font-size: 88%; } @@ -1172,6 +1183,9 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { .page-content { padding: 0; } + #question2answer-plugins ~ ul:not(:nth-last-child(2)) { + margin-bottom: 2rem; + } #question2answer-plugins ~ ul .child-repository, #question2answer-themes ~ ul .child-repository { padding-left: 1.5rem; } From 93f502001e362df44c219e13a59fd449bcc10c4d Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 09:38:34 +0000 Subject: [PATCH 16/29] Replace Emoji colors on DOM load --- js/scripts.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/js/scripts.js b/js/scripts.js index e1a3062..65d3521 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -425,27 +425,27 @@ if(gitLinks != null && gitLinks.length) { return Object.values(storage); } + // Change Emoji colors for actual color element + const colorLegends = document.querySelector('.template-addons-plugins blockquote, .template-addons-themes blockquote'); + const colorLegendsUpdated = colorLegends.innerHTML.replace( + '🟢','' + ).replace( + '🔵','' + ).replace( + '🟡','' + ).replace( + '🔴','' + ).replace( + '🔘','' + ); + colorLegends.innerHTML = colorLegendsUpdated; + // Create tags for both - Plugins and Themes const createTags = (param) => { // get stored current Q2A version const currentQ2aVersion = Object.values(param[0] || {} )[3]; - // Change Emoji colors for actual color element - const colorLegends = document.querySelector('.template-addons-plugins blockquote, .template-addons-themes blockquote'); - const colorLegendsUpdated = colorLegends.innerHTML.replace( - '🟢','' - ).replace( - '🔵','' - ).replace( - '🟡','' - ).replace( - '🔴','' - ).replace( - '🔘','' - ); - colorLegends.innerHTML = colorLegendsUpdated; - param.forEach((item, index) => { const list = param.slice(2); // Remove list header (metadata) indexes From 6341eb12a19f81fabe602c9cc898a1e32ef22883 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 19:31:31 +0000 Subject: [PATCH 17/29] Markdown Editor description --- addons/plugins.md | 4 ++-- css/styles.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/plugins.md b/addons/plugins.md index 4bfdc69..7111c41 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -29,8 +29,8 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Embed Youtube Plugin](https://github.com/NoahY/q2a-embed) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Converts links to YouTube videos into embedded videos when displaying questions, answers or comments. - [Express Editor](https://github.com/amiyasahu/q2a-express-editor) by [Amiya Sahu](http://amiyasahu.com/). Recent version of [CKEditor](http://ckeditor.com/) bundled with [Ace Editor](http://ace.c9.io/) for smooth code editing. - [Lightbox-images](https://github.com/ProThoughts/q2apro-lightbox-images) by [q2apro.com](https://github.com/q2apro). Provides a Lightbox Effect for all images in posts. -- [Markdown Editor Plugin](https://github.com/ProThoughts/markdown-editor-for-q2a) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). -- ➔ [Markdown Editor Plugin Updated](https://github.com/fardina/markdown-editor-for-q2a) updated for Q2A 1.8.6 +- [Markdown Editor](https://github.com/ProThoughts/markdown-editor-for-q2a) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). This plugin uses [Markdown](https://www.markdownguide.org/getting-started/), a lightweight markup language for creating formatted text using a plain-text editor. For example `**bold**` for bold text, or start a line with `>` for quoting sources. +- ➔ [Markdown Editor forked](https://github.com/fardina/markdown-editor-for-q2a) by [Fardina](https://github.com/fardina) for Q2A 1.8.6, with side-by-side `Markdown` and `WYSIWYG` preview. Screenshot [here](https://raw.githubusercontent.com/fardina/markdown-editor-for-q2a/refs/heads/master/screenshot.png). - [Memes for Text](https://github.com/thibaultduponchelle/q2a-memes) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). Post *lolfaces* in your answers or comments. - [Miranda](http://www.question2answer.org/qa/51849/tinymcewrapper-miranda-updated-most-powerful-editor-joins) [Tinymcse Wrapper](https://www.tinymce.com/) by Don Shakespeare. Adds Tinymcse to be used across posts as well as other text areas like messages, wall posts and is higly customizable. - [Prevent Simultaneous Edits](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [q2apro.com](https://github.com/q2apro). Prevent two users editing the same post simultaneously. diff --git a/css/styles.css b/css/styles.css index dd1e4f8..d446319 100644 --- a/css/styles.css +++ b/css/styles.css @@ -661,7 +661,7 @@ ul.sub-nav { background-color: rgb(0 0 0 / 2%); } -#question2answer-plugins ~ ul li a:first-child, #question2answer-themes ~ ul li a:first-child { +#question2answer-plugins ~ ul li a:first-of-type, #question2answer-themes ~ ul li a:first-of-type { font-weight: bold; } From 1628583bf610dd924ef5f731d5e868e939e0cbb8 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:29:28 +0000 Subject: [PATCH 18/29] Brought back the original Markdown Editor link Brought back the original Markdown Editor link by Scott, since it still works with version Q2A 1.8.8 , and these seem to be different plugins. (Aesthetic wise) I prefer the simpler version by Scott, which other people might find preferable as well, so this gives the option to download the original one. --- addons/plugins.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/plugins.md b/addons/plugins.md index 7111c41..b4d0607 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -29,8 +29,9 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Embed Youtube Plugin](https://github.com/NoahY/q2a-embed) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Converts links to YouTube videos into embedded videos when displaying questions, answers or comments. - [Express Editor](https://github.com/amiyasahu/q2a-express-editor) by [Amiya Sahu](http://amiyasahu.com/). Recent version of [CKEditor](http://ckeditor.com/) bundled with [Ace Editor](http://ace.c9.io/) for smooth code editing. - [Lightbox-images](https://github.com/ProThoughts/q2apro-lightbox-images) by [q2apro.com](https://github.com/q2apro). Provides a Lightbox Effect for all images in posts. -- [Markdown Editor](https://github.com/ProThoughts/markdown-editor-for-q2a) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). This plugin uses [Markdown](https://www.markdownguide.org/getting-started/), a lightweight markup language for creating formatted text using a plain-text editor. For example `**bold**` for bold text, or start a line with `>` for quoting sources. -- ➔ [Markdown Editor forked](https://github.com/fardina/markdown-editor-for-q2a) by [Fardina](https://github.com/fardina) for Q2A 1.8.6, with side-by-side `Markdown` and `WYSIWYG` preview. Screenshot [here](https://raw.githubusercontent.com/fardina/markdown-editor-for-q2a/refs/heads/master/screenshot.png). +- [Markdown Editor](https://github.com/svivian/q2a-markdown-editor) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). This plugin uses [Markdown](https://www.markdownguide.org/getting-started/), a lightweight markup language for creating formatted text using a plain-text editor. For example `**bold**` for bold text, or start a line with `>` for quoting sources. +- ➔ [Markdown Editor with TOAST UI](https://github.com/ProThoughts/markdown-editor-for-q2a) by **awei922**. This version is based off of **Scott Vivian**'s version & [tui.editor](https://ui.toast.com/tui-editor), and allows you to edit your Markdown documents using text or WYSIWYG. Screenshot [here](https://raw.githubusercontent.com/ProThoughts/markdown-editor-for-q2a/refs/heads/master/screenshot.png). +- ➔ **awei922**'s version of **Markdown with TUI** also [forked here](https://github.com/fardina/markdown-editor-for-q2a) by [Fardina](https://github.com/fardina) for Q2A 1.8.6. - [Memes for Text](https://github.com/thibaultduponchelle/q2a-memes) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). Post *lolfaces* in your answers or comments. - [Miranda](http://www.question2answer.org/qa/51849/tinymcewrapper-miranda-updated-most-powerful-editor-joins) [Tinymcse Wrapper](https://www.tinymce.com/) by Don Shakespeare. Adds Tinymcse to be used across posts as well as other text areas like messages, wall posts and is higly customizable. - [Prevent Simultaneous Edits](https://github.com/ElephantsGroup/q2a-prevent-simultaneous-edits) by [q2apro.com](https://github.com/q2apro). Prevent two users editing the same post simultaneously. From 713c30199ede3fcc0a976ca87e8e0126cf8e3c4d Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 23 Nov 2024 23:35:42 +0000 Subject: [PATCH 19/29] ReOrdered Plugins Alphabetically --- addons/plugins.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/addons/plugins.md b/addons/plugins.md index b4d0607..8c246e2 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -61,6 +61,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Facebook Like Box](https://github.com/amiyasahu/q2a-facebook-like-box) by [Amiya Sahu](http://amiyasahu.com/). Basic widget for displaying a [Facebook](http://www.facebook.com/) like box. - [Google Adsense Async](https://github.com/ganbox/ganbox-async-adsense) by Georgi Stefanov @ [Ganbox](https://ganbox.com/). Widget for displaying Google AdSense ads with asynchronous JavaScript code. - [Google Cloud Storage](https://q2a.io/product/google-cloud-storage-plugin/) by [q2a.io](https://q2a.io/). Stores and serves uploaded files in [Google Cloud Storage](https://cloud.google.com/storage/) instead of the local database. +- [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) by [Arjun](http://gateoverflow.in/user/Arjun). Handles the trouble of adding MathJax or CodePretiffy to Q2A.- [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) by [Arjun](http://gateoverflow.in/user/Arjun). Handles the trouble of adding MathJax or CodePretiffy to Q2A. - [Google Custom Search](https://github.com/arjunsuresh/q2a-google-search) by [Arjun](http://gateoverflow.in/user/Arjun). Replaces Q2A's default search functionality with [Google Custom Search](https://cse.google.com/cse/all). Useful for searching across a network of Q2A sites and also if you are using blogs in Q2A. - [Google Plus Badge](https://github.com/amiyasahu/q2a-google-plus-badge) by [Amiya Sahu](http://amiyasahu.com/). Basic widget for displaying a [Google Plus](https://plus.google.com/) badge. - [IMAP Login](https://github.com/josebmera/q2a-imap-login) by Jose Mª Bermudo Mera. Replaces the Q2A login form with authentication against an email server via IMAP. @@ -70,7 +71,6 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Mailgun Integration](https://q2a.io/product/mailgun-integration-plugin/) by [q2a.io](https://q2a.io/). Sends outgoing emails via the [Mailgun](https://www.mailgun.com/) API. - [Mailing List Manager](https://github.com/MichaelApproved/Question2Answer-Mailing-List-Manager) by [Michael Khalili](http://www.michaelapproved.com/). Adds your Q2A member email addresses to your [MailChimp.com](http://mailchimp.com/) list. - [Mattermost Notifications](https://github.com/Schoaf/qa-mattermost-notifications) by [Andreas Scharf](https://github.com/Schoaf). Sends notifications of new questions to a [Mattermost](http://mattermost.org/) feed. -- [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) by [Arjun](http://gateoverflow.in/user/Arjun). Handles the trouble of adding MathJax or CodePretiffy to Q2A. - [MyBB single sign-on](http://community.mybb.com/thread-121885-post-879492.html#pid879492) by Dahevos. Allows Q2A to be integrated into a MyBB forum, sharing the same user database. - [Open Login](https://github.com/alixandru/q2a-open-login) by [Alex Lixandru](https://github.com/alixandru). Log in via [Facebook](http://www.facebook.com/), [Google](http://www.google.com/), [Yahoo](http://www.yahoo.com/), [Github](http://www.github.com/) and many more, plus multiple logins for a single Q2A account. - [Persian Magic Words](http://question2answer-farsi.com/707/q2a-magicword-plugin.html) by AmirHossein Tavousi @ [Question2Answer Farsi](http://question2answer-farsi.com/). Shows word meanings from [Vajehyab.com](http://vajehyab.com/) for highlighted words. @@ -105,7 +105,6 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q ## Widgets - [Category Anywhere](https://github.com/ostack/qa-categories-anywhere-widget) by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146). [Demo](https://www.ostack.cn). Displays category not only sidebar but also top panel, bottom panel and so on. -- [Random Question](https://github.com/ostack/qa-random-question-widget) by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146). [Demo](https://www.ostack.cn). Displays random questions, to improve search engine coverage. - [Breadcrumbs Widget](https://github.com/amiyasahu/q2a-breadcrumbs) by [Amiya Sahu](http://amiyasahu.com/). Displays navigational breadcrumbs showing the path to the currently viewed page. - [Category Description](https://github.com/arjunsuresh/categorydescription) by [Arjun](http://gateoverflow.in/user/Arjun). Allows HTML description to be added for Categories which gets displayed on top of Category Pages. - [Feed Widget](https://github.com/Towhidn/Q2A-feed-widget) by [Towhid](http://TowhidN.com/). Lists recent items from an external RSS feed. Useful for blog posts, articles, etc... @@ -115,6 +114,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - ➔ Also [forked here](https://github.com/pupi1985/q2a-onlineusers) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). - [Popular Questions](https://github.com/ProThoughts/q2apro-popular-questions-widget) by [q2apro.com](https://github.com/q2apro). Shows a list of the most viewed questions. - [Random Question Widget](https://github.com/NoahY/qa-random-question) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Displays a random question in the sidebar. +- ➔ [Random Question updated](https://github.com/ostack/qa-random-question-widget) by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146). [Demo](https://www.ostack.cn). Displays random questions, to improve search engine coverage. - [Recent Events Widget](https://github.com/fullstack412/Q2A-Recent-Events-Widget) by [q2apro.com](https://github.com/q2apro). Displays all recent events in the sidebar or main area. - [Recent Questions Widget](http://www.q2amarket.com/market/q2am-recent-questions/) by [Q2A Market](http://www.q2amarket.com/). Shows a list of recent questions in the sidebar or other locations. - [RSS Feed Widget](https://github.com/mbentley3123/q2a-rss-feed) by Mark Bentle by Mark Bentleyy. Displays posts from an RSS feed in the sidebar. @@ -130,7 +130,6 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q ## User interface -- [Title Length Counter](https://github.com/MominRaza/title-length-counter) by [Momin Raza](https://github.com/MominRaza). It shows the length of title below title text field. - [Advanced Tag Descriptions](https://github.com/Towhidn/q2a-tag-descriptions) by [Towhid](http://TowhidN.com/). Add titles, icons and HTML descriptions to your tags. - [Accept Rate Plugin](https://github.com/NoahY/q2a-accept-rate) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Shows the percentage of questions asked by a user which had an answer selected as the best. - [Ajax User Search](https://github.com/ProThoughts/q2apro-ajax-usersearch) by [q2apro](https://github.com/q2apro). Adds a live search box at the top of the users page to search users by username. @@ -146,12 +145,14 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Gregorian 2 Jalali](http://question2answer-farsi.com/109/shamsi-date-plugin.html) by Ali Sayahiyan @ [Question2Answer Farsi](http://question2answer-farsi.com/). Displays dates in Jalali (Iranian) format. - [Guest-id](https://github.com/ProThoughts/q2apro-guest-id) by [q2apro.com](https://github.com/q2apro). Gives all anonymous users that are posting in your forum a unique ID. - [Hide answer for unlogin user](https://github.com/ostack/q2a-hide-answers-for-unlogin-user) by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146). [Demo](https://www.ostack.cn). Only show answers for logged in users or users with enough points, to encourage registrations. +- [MTL - Maximum Tag Length](https://www.question2answer.org/qa/41822) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Limits the amount of characters allowed per each question tag. - [Pretty-tags](https://github.com/ProThoughts/q2apro-pretty-tags) by [q2apro.com](https://github.com/q2apro). Provides a pretty autocomplete for tags on the ask page. - [Privilege Management](https://github.com/NoahY/q2a-privileges) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds a tab to user profile pages showing progress towards additional privileges. - [Private Message Adapter](https://github.com/JacksiroKe/q2a-pm-adapter) by [Jack Siro](https://www.linkedin.com/in/jacksiroke/). Adds an editor of your choice on the private message and feedback pages, including support for HTML messages. - [Question Filter](https://github.com/ganbox/qa-filter) by Georgi Stefanov @ [Ganbox](http://ganbox.com/). Adds filtering of user input when asking questions, to check duplication, capitalization, etc... - [Question Numbering](https://github.com/arjunsuresh/q2a-qnumbering) by [Arjun](http://gateoverflow.in/user/Arjun). Adds numbering to questions in question listing pages. - [Quick-login](https://github.com/ProThoughts/q2apro-quick-login) by [q2apro.com](https://github.com/q2apro). Provides a quick login field for all your users. +- [RA - Random Avatar](https://www.question2answer.org/qa/39812) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Assigns new users a random avatar from a given set immediately after registering. - [Role Markers](https://github.com/NoahY/q2a-role-markers) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds markers to the avatars displayed for users with special privileges. - ➔ Also [forked](https://github.com/zakkak/q2a-role-markers) by [Zakkak](https://github.com/zakkak). - [Social Sharing Plugin](https://github.com/NoahY/q2a-share) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Adds social sharing buttons to question pages. @@ -159,6 +160,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Tag Search](https://github.com/arjunsuresh/tag-search) by [Arjun](http://gateoverflow.in/user/Arjun). Allows tags to be searched via AJAX and with auto complete. - [Tag Descriptions Plugin](http://www.question2answer.org/releases/q2a-tag-descriptions-tutorial.zip) from the [plugin tutorial]({{ site.baseurl }}/plugins/tutorial/). Allows tag descriptions to be shown on tag pages and in tooltips. - [Theme Switcher Plugin](https://github.com/arjunsuresh/q2a-theme-switch) by [NoahY](http://www.question2answer.org/qa/user/NoahY) and updated by [Arjun](http://gateoverflow.in/user/Arjun). Allows users to choose their own theme on their profile page for normal as well as mobile view. +- [Title Length Counter](https://github.com/MominRaza/title-length-counter) by [Momin Raza](https://github.com/MominRaza). It shows the length of title below title text field. - [Top Searches](https://github.com/arjunsuresh/q2a-top-search) by [Arjun](http://gateoverflow.in/user/Arjun). Adds a widget to display the top searched words. - ➔ Also have an option to display top searched tags if using [Tag Search](https://github.com/arjunsuresh/tag-search) plugin. - [User Signatures Plugin](https://github.com/NoahY/q2a-signatures) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to define a signature for all of their posts. @@ -166,8 +168,6 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Userinfo](https://github.com/ProThoughts/q2apro-userinfo) by [q2apro.com](https://github.com/q2apro). Mouse over a username to display user profile information: Avatar image, account age, total points, monthly points, answers, best answers, ratio, questions posted, badges. - [Userslist-locations](https://github.com/ProThoughts/q2apro-userslist-locations) by [q2apro.com](https://github.com/q2apro). Add Location to users list - [Warn on Leave](https://github.com/yshiga/q2apro-warn-on-leave) by [q2apro.com](https://github.com/q2apro). Warns users before leaving a page with text they have entered (also support WYSIWYG editor). -- [MTL - Maximum Tag Length](https://www.question2answer.org/qa/41822) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Limits the amount of characters allowed per each question tag. -- [RA - Random Avatar](https://www.question2answer.org/qa/39812) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Assigns new users a random avatar from a given set immediately after registering. - [Paid] [PC - Profile Customizer](https://www.question2answer.org/qa/36952) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows the customization of the profile page by adding additional features not present in the core, including tracking of point changes. - [Paid] [SNP - Social Network Profiles](https://www.question2answer.org/qa/46857) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows users to add information about social networks they are part of and share it with the community. - [Paid] [UR - User Reactions](https://www.question2answer.org/qa/62711) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows users to react to questions, answers and comments. @@ -178,11 +178,11 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Email Formatting](https://github.com/ruuttt/q2a-email-formating) by Ruut Brandsma @ [Ecofys](http://www.ecofys.com/). Allows HTML formatting to be used in notification emails. - [Email Notifications](https://github.com/sawtoothsoftware/Q2A-Email-Notifications) by Walter Williams @ [Sawtooth Software](http://www.sawtoothsoftware.com/). Allows users to receive notifications about new questions and answers. - [Follow Question](https://github.com/ProThoughts/q2apro-follow-question) by [q2apro.com](https://github.com/q2apro). +- [Newsletter-favtags](https://github.com/ProThoughts/q2apro-newsletter-favtags) by [q2apro.com](https://github.com/q2apro).Users can subscribe to their favorite tags and the plugin emails them daily if there are new questions. +- [Newsletter Plugin](https://github.com/NoahY/q2a-newsletter) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to receive an HTML newsletter of the latest top questions, answers and comments. - [On-Site Notifications](https://github.com/q2apro/q2apro-on-site-notifications) by [q2apro.com](https://github.com/q2apro). Pop-up notifications on the page which can replace email notifications. - [Remind Users](https://github.com/yshiga/q2apro-remind-users) by [q2apro.com](https://github.com/q2apro). Reminds users to complete their profile and upload an avatar within X hours after registration. - [Revised Email Notifications](https://github.com/zakkak/q2a-email-notifications-revised) by [Foivos Zakkak](http://foivos.zakkak.net/). An enhanced fork of the "Email Notifications" plugin. -- [Newsletter-favtags](https://github.com/ProThoughts/q2apro-newsletter-favtags) by [q2apro.com](https://github.com/q2apro).Users can subscribe to their favorite tags and the plugin emails them daily if there are new questions. -- [Newsletter Plugin](https://github.com/NoahY/q2a-newsletter) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to receive an HTML newsletter of the latest top questions, answers and comments. - [Tag and Category Notification](https://github.com/amiyasahu/q2a-email-notification) by [Amiya Sahu](http://amiyasahu.com/). When a question is asked with a tag/category, emails users who favorited that tag/category. - [Votes-manager](https://github.com/ProThoughts/q2apro-votes-manager) by [q2apro.com](https://github.com/q2apro). Lists all votes made in your forum. Sends emails to users about activities on questions they have favorited. @@ -207,6 +207,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Database Backup/Restore](https://github.com/KrzysztofKielce/q2a-backup) by [Krzysztof Kielce](http://www.question2answer.org/qa/user/Krzysztof+Kielce). An admin tool for one-click backup and restore of the Q2A instance or entire database. - [Delete Hidden Posts](https://github.com/amiyasahu/q2a-delete-hidden-posts) by [Amiya Sahu](http://amiyasahu.com/). Enables all hidden posts (including those with dependencies) to be deleted. - [Expert Questions](https://github.com/NoahY/q2a-expert-questions) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows some questions to made viewable by experts only. +- [FC - Fake Cron](https://github.com/pupi1985/q2a-fake-cron) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Fires events after a certain amount of requests or in a time-based manner (daily, weekly, monthly). [Q2A Post](https://www.question2answer.org/qa/42051) - [Find Text in Posts](https://github.com/q2apro/q2apro-find-text-posts) by [q2apro](https://github.com/q2apro). Allows admins to search posts for specific text and display the creator, URL and content. - [Google Analytics Plugin](https://github.com/kufeiko/QA-Google-Analytics-Plugin) by [Ivan](http://www.question2answer.org/qa/user/Ivan+Donkov). Analyze Q2A traffic with Google Analytics, optionally excluding requests from the super admin. - [Greeklish URLs](https://github.com/stefkiourk/Question2Answer-greeklish-URLs) by Kiourkoulis Stefanos]. Converts Greek characters in question URLs into Latin equivalents. @@ -226,7 +227,6 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Uploadmanager](https://github.com/ProThoughts/q2apro-uploadmanager) by [q2apro.com](https://github.com/q2apro). A complete upload manager for question2answer with upload details and image rotate features. - [Userstatistics](https://github.com/ProThoughts/q2apro-userstatistics) by [q2apro.com](https://github.com/q2apro). A complete user statistic with detailed points per post and thumbs and more. - [Votes-manager](https://github.com/ProThoughts/q2apro-votes-manager) by [q2apro.com](https://github.com/q2apro). Lists all votes made in your forum. -- [FC - Fake Cron](https://www.question2answer.org/qa/42051) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Fires events after a certain amount of requests or in a time-based manner (daily, weekly, monthly). - [Paid] [BCG - Bulk Content Generator](https://www.question2answer.org/qa/54488) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows admins to generate large amounts of content on their sites simply by uploading files (.xls, .xlsx, .ods, among other file formats). - [Paid] [JABP - Just A Bump Plugin](https://www.question2answer.org/qa/31264) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows users to bump questions sending them to the top of the All activity section. - [Paid] [TUB - Temporary User Block](https://www.question2answer.org/qa/73008) by [pupi1985](https://www.question2answer.org/qa/user/pupi1985). Allows users to be blocked for a period of time and then be automatically unblocked. From e1d02deca51431ab9e6243c390f9733faca861b4 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sun, 24 Nov 2024 00:53:00 +0000 Subject: [PATCH 20/29] Added pupi1985 newest free plugins and mine as well --- addons/plugins.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/plugins.md b/addons/plugins.md index 8c246e2..230cbd3 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -177,6 +177,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Category Email Notifications](https://github.com/dunse/qa-category-email-notifications) by Pehr Johansson. Emails new questions in a category to all users who favorited that category. - [Email Formatting](https://github.com/ruuttt/q2a-email-formating) by Ruut Brandsma @ [Ecofys](http://www.ecofys.com/). Allows HTML formatting to be used in notification emails. - [Email Notifications](https://github.com/sawtoothsoftware/Q2A-Email-Notifications) by Walter Williams @ [Sawtooth Software](http://www.sawtoothsoftware.com/). Allows users to receive notifications about new questions and answers. +- [FNS - Flexible Notifications System](https://github.com/pupi1985/q2a-pupi-fns) by [pupi1985](https://question2answer.org/qa/user/pupi1985). Flexible Notifications System is a plugin that allows users to receive notifications in a flexible and efficient way. [Learn more](https://question2answer.org/qa/112052/new-free-plugin-fns-flexible-notifications-system) - [Follow Question](https://github.com/ProThoughts/q2apro-follow-question) by [q2apro.com](https://github.com/q2apro). - [Newsletter-favtags](https://github.com/ProThoughts/q2apro-newsletter-favtags) by [q2apro.com](https://github.com/q2apro).Users can subscribe to their favorite tags and the plugin emails them daily if there are new questions. - [Newsletter Plugin](https://github.com/NoahY/q2a-newsletter) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows users to receive an HTML newsletter of the latest top questions, answers and comments. @@ -191,9 +192,11 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Akismet Plugin](http://qcybb.com/question2answer-akismet-plugin/) by [Dave Hannon](http://qcybb.com/). Uses the [Akismet](http://akismet.com/) spam filtering service to detect spammy posts and queue them for moderation. - [AntiBot Captcha](https://github.com/KrzysztofKielce/q2a-captcha-antibot) by [Krzysztof Kielce](http://www.question2answer.org/qa/user/Krzysztof+Kielce). Simple and easy captcha for Q2A 1.5+. - [Bootstrap reCAPTCHA](https://github.com/amiyasahu/q2a-bootstrap-recaptcha) by [Amiya Sahu](http://amiyasahu.com/). A version of the default reCAPTCHA plugin that works well with Bootstrap-based themes. +- [Q2A Honeypot](https://github.com/rxchun/q2a-honeypot) by [Chun](https://rxchun.github.io/). This plugin creates a simple Javascript honeypot for spam bots on Register, Ask, and Feedback pages, preventing them from auto-filling fields and completing user registration. - [KeyCAPTCHA Captcha](https://github.com/spd-name/q2a-captcha-keycaptcha) by [Andrey Gvosdev](http://spd.name). Provides the [KeyCAPTCHA](https://www.keycaptcha.com) captcha for Q2A. - [Logical Captcha](https://github.com/amiyasahu/q2a-logical-captcha) by [Amiya Sahu](http://amiyasahu.com/). Shows simple logical questions from [Text CAPTCHA](http://textcaptcha.com) as a captcha challenge. - [Spammer Checker](https://github.com/sawtoothsoftware/Q2A-Spammer-Checker) by Walter Williams @ [Sawtooth Software](http://www.sawtoothsoftware.com/). On IP address pages, checks [stopforumspam.com](http://www.stopforumspam.com/) and [botscout.com](http://botscout.com/) to see if the IP belong to a known spammer. +- [SRS - SPAM Registration Stopper](https://github.com/pupi1985/q2a-pupi-srs) by [pupi1985](https://question2answer.org/qa/user/pupi1985). SPAM Registration Stopper is a plugin that prevents highly probable SPAM user registrations based on well-known SPAM checking services. [Learn more](https://question2answer.org/qa/98717/new-free-plugin-srs-spam-registration-stopper) - [Stop-spam](https://github.com/ProThoughts/q2apro-stop-spam) by [q2apro.com](https://github.com/q2apro). An easy to use customizable captcha with a honeypot that keeps 99 % of the daily spam away. ## Administrative tools @@ -203,6 +206,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - ➔ [Batch Import Users Updated](https://github.com/ostack/q2a-user-import) updated and fixed by [ZhaoGuangyue](https://www.linkedin.com/in/%E5%85%89%E8%B7%83-%E8%B5%B5-b58234146). - [Categorization Plugin](https://github.com/NoahY/q2a-cat) by [NoahY](http://www.question2answer.org/qa/user/NoahY). Allows individual categorization of uncategorized posts via the categories admin panel. - [Change Post Owner](https://github.com/ProThoughts/q2apro-change-post-owner) by [q2apro.com](https://github.com/q2apro). Allows the author of a post to be changed, or to make it anonymous. +- [COMA - Configuration Manager](https://github.com/pupi1985/q2a-pupi-coma) by [pupi1985](https://question2answer.org/qa/user/pupi1985). This plugin allows users to import/export and share their Q2A configuration with others. This is particularly useful when trying to identify issues with the Q2A core or plugins. [Learn more](https://question2answer.org/qa/98717/new-free-plugin-srs-spam-registration-stopper) - [Convert Comment to Answer](https://github.com/ellcupakabra/q2apro-comment-to-answer) by [q2apro.com](https://github.com/q2apro). Convert a comment to an answer, optionally move the succeeding comments. - [Database Backup/Restore](https://github.com/KrzysztofKielce/q2a-backup) by [Krzysztof Kielce](http://www.question2answer.org/qa/user/Krzysztof+Kielce). An admin tool for one-click backup and restore of the Q2A instance or entire database. - [Delete Hidden Posts](https://github.com/amiyasahu/q2a-delete-hidden-posts) by [Amiya Sahu](http://amiyasahu.com/). Enables all hidden posts (including those with dependencies) to be deleted. From 4d2e1a03e2335fe4c14f7fe6100ca7ca3a7464fd Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sun, 24 Nov 2024 03:08:22 +0000 Subject: [PATCH 21/29] Removed duplicate link inside the same instance --- addons/plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/plugins.md b/addons/plugins.md index 230cbd3..229e545 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -61,7 +61,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Facebook Like Box](https://github.com/amiyasahu/q2a-facebook-like-box) by [Amiya Sahu](http://amiyasahu.com/). Basic widget for displaying a [Facebook](http://www.facebook.com/) like box. - [Google Adsense Async](https://github.com/ganbox/ganbox-async-adsense) by Georgi Stefanov @ [Ganbox](https://ganbox.com/). Widget for displaying Google AdSense ads with asynchronous JavaScript code. - [Google Cloud Storage](https://q2a.io/product/google-cloud-storage-plugin/) by [q2a.io](https://q2a.io/). Stores and serves uploaded files in [Google Cloud Storage](https://cloud.google.com/storage/) instead of the local database. -- [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) by [Arjun](http://gateoverflow.in/user/Arjun). Handles the trouble of adding MathJax or CodePretiffy to Q2A.- [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) by [Arjun](http://gateoverflow.in/user/Arjun). Handles the trouble of adding MathJax or CodePretiffy to Q2A. +- [Google Code Prettify and MathJax](https://github.com/arjunsuresh/mathjax) by [Arjun](http://gateoverflow.in/user/Arjun). Handles the trouble of adding MathJax or CodePretiffy to Q2A. - [Google Custom Search](https://github.com/arjunsuresh/q2a-google-search) by [Arjun](http://gateoverflow.in/user/Arjun). Replaces Q2A's default search functionality with [Google Custom Search](https://cse.google.com/cse/all). Useful for searching across a network of Q2A sites and also if you are using blogs in Q2A. - [Google Plus Badge](https://github.com/amiyasahu/q2a-google-plus-badge) by [Amiya Sahu](http://amiyasahu.com/). Basic widget for displaying a [Google Plus](https://plus.google.com/) badge. - [IMAP Login](https://github.com/josebmera/q2a-imap-login) by Jose Mª Bermudo Mera. Replaces the Q2A login form with authentication against an email server via IMAP. From e68aa7a8506b91ffa9deaa32ee705a95ea209de1 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sun, 24 Nov 2024 04:23:03 +0000 Subject: [PATCH 22/29] CSS fix --- css/styles.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/css/styles.css b/css/styles.css index d446319..7287c76 100644 --- a/css/styles.css +++ b/css/styles.css @@ -627,7 +627,8 @@ ul.sub-nav { padding: 0; } -#question2answer-plugins ~ ul:not(:nth-last-child(2)) { +#question2answer-plugins ~ ul:not(:nth-last-child(2)), +#question2answer-themes ~ ul:not(:nth-last-child(2)) { margin-bottom: 3rem; } @@ -1183,7 +1184,8 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { .page-content { padding: 0; } - #question2answer-plugins ~ ul:not(:nth-last-child(2)) { + #question2answer-plugins ~ ul:not(:nth-last-child(2)), + #question2answer-themes ~ ul:not(:nth-last-child(2)) { margin-bottom: 2rem; } #question2answer-plugins ~ ul .child-repository, #question2answer-themes ~ ul .child-repository { From 1307d4c7c42dc7d0386c8ec5c7ec44d498566898 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sun, 24 Nov 2024 06:43:38 +0000 Subject: [PATCH 23/29] Fix for weird indentations on GitHub but not IDE --- css/styles.css | 126 +++---- js/scripts.js | 870 ++++++++++++++++++++++++------------------------- 2 files changed, 498 insertions(+), 498 deletions(-) diff --git a/css/styles.css b/css/styles.css index 7287c76..f24e049 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1,5 +1,5 @@ /* ================== - Base styles + Base styles ================== */ /* Colors: @@ -48,40 +48,40 @@ h1, h2, h3 { /* h1, h2, h3, h4, h5, h6 { - color: #424242; + color: #424242; } h1 { - font-size: 2rem; - line-height: 2.5rem; - margin: 0 0 .5rem; + font-size: 2rem; + line-height: 2.5rem; + margin: 0 0 .5rem; } h2 { - font-size: 1.5rem; - line-height: 2rem; - margin: 2rem 0 .5rem; + font-size: 1.5rem; + line-height: 2rem; + margin: 2rem 0 .5rem; } h3 { - font-size: 1.25rem; - font-weight: 500; - line-height: 1.75rem; - margin: 1.5rem 0 .5rem; + font-size: 1.25rem; + font-weight: 500; + line-height: 1.75rem; + margin: 1.5rem 0 .5rem; } h4 { - font-size: 1rem; - font-weight: 500; - line-height: 1.5rem; - margin: 1.25rem 0 .25rem; + font-size: 1rem; + font-weight: 500; + line-height: 1.5rem; + margin: 1.25rem 0 .25rem; } h5 { - font-size: .875rem; - font-weight: inherit; - line-height: 1.5rem; - margin: 1rem 0 .25rem; + font-size: .875rem; + font-weight: inherit; + line-height: 1.5rem; + margin: 1rem 0 .25rem; } */ @@ -125,13 +125,13 @@ pre { pre code { background-color: #f6f6f6; padding: 0; - max-height: 600px; + max-height: 600px; color: inherit; font-family: inherit; tab-size: 4; font-size: 13px; - overflow: auto; - padding: 12px; + overflow: auto; + padding: 12px; } blockquote { @@ -142,7 +142,7 @@ blockquote { } /* =================== - PREDEFINED CLASSES + PREDEFINED CLASSES ======================= */ .no-select, .noSelect, .noselect { @@ -237,7 +237,7 @@ blockquote { /* ================== - LAYOUT + LAYOUT ================== */ body.page { @@ -443,7 +443,7 @@ ul.sub-nav { } /* =============== - BREADCRUMBS + BREADCRUMBS =============== */ .breadcrumbs { @@ -478,7 +478,7 @@ ul.sub-nav { /* =============== - MAIN SECTION + MAIN SECTION ================ */ .notice-container { @@ -644,7 +644,7 @@ ul.sub-nav { } .child-repository { - position: relative; + position: relative; } .child-repository:before { @@ -668,8 +668,8 @@ ul.sub-nav { .repository-footer { display: flex; - margin-top: .2rem; - color: rgb(95, 99, 104); + margin-top: .2rem; + color: rgb(95, 99, 104); font-size: 88%; } @@ -711,7 +711,7 @@ ul.sub-nav { } .rep-date-5 { /* background-color: #e0e0e0; */ /* Grey */ - background-color: #e74c3c; + background-color: #e74c3c; color: #ffffff; } /* @@ -847,7 +847,7 @@ ul.sub-nav { /* ================== - HOMEPAGE + HOMEPAGE ================== */ .home-intro { @@ -921,7 +921,7 @@ ul.sub-nav { /* =============== - DOCS PAGES + DOCS PAGES =============== */ a[href*="/releases/question2answer"][href$=".zip"] { @@ -942,7 +942,7 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { /* =============== - MEDIA QUERIES + MEDIA QUERIES ================= */ @media (min-width: 1500px) { @@ -1014,9 +1014,9 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { .page-content { padding: 3rem; } - .page-content .sectionTitle:first-child { - margin-top: 0; - } + .page-content .sectionTitle:first-child { + margin-top: 0; + } .page-sidebar { padding: 2rem 1rem; } @@ -1029,7 +1029,7 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { /* ======================== - MEDIA QUERIES MAX-WIDTH + MEDIA QUERIES MAX-WIDTH =========================== */ @media (max-width: 1120px) { @@ -1135,7 +1135,7 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { .showcase-cols { grid-gap: 1rem; - margin: 2rem 1rem; + margin: 2rem 1rem; } .card-showcase { padding: 1rem .5rem; @@ -1147,11 +1147,11 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { font-size: 1rem; margin-right: .5rem; } - .card-showcase-info p { - margin-top: .5rem; - } - - .page-content { + .card-showcase-info p { + margin-top: .5rem; + } + + .page-content { border: none; } @@ -1184,34 +1184,34 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { .page-content { padding: 0; } - #question2answer-plugins ~ ul:not(:nth-last-child(2)), - #question2answer-themes ~ ul:not(:nth-last-child(2)) { - margin-bottom: 2rem; - } - #question2answer-plugins ~ ul .child-repository, #question2answer-themes ~ ul .child-repository { - padding-left: 1.5rem; - } - .child-repository:before { - left: .5rem; - } - .rep-date-true { - white-space: nowrap; - } + #question2answer-plugins ~ ul:not(:nth-last-child(2)), + #question2answer-themes ~ ul:not(:nth-last-child(2)) { + margin-bottom: 2rem; + } + #question2answer-plugins ~ ul .child-repository, #question2answer-themes ~ ul .child-repository { + padding-left: 1.5rem; + } + .child-repository:before { + left: .5rem; + } + .rep-date-true { + white-space: nowrap; + } .page-status { padding: .6rem 1rem; } } @media (max-width: 375px) { - .twbb { - display: block; - font-size: 12px; - margin-right: .5rem; - } + .twbb { + display: block; + font-size: 12px; + margin-right: .5rem; + } } /* ==================== - Syntax highlighting + Syntax highlighting ======================= */ .highlight .hll { background-color: #ffffcc } diff --git a/js/scripts.js b/js/scripts.js index 65d3521..f3e220d 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -5,36 +5,36 @@ const daysUntilNextFetch = 7; class githubList { constructor() { this.pluginList = []; - this.themeList = []; + this.themeList = []; } - addListLink(listType, index, linkInfo, dateInfo, q2aVersion) { + addListLink(listType, index, linkInfo, dateInfo, q2aVersion) { // Push object to array, that later will be saved locally - if(listType === 'plugins') { - this.pluginList.push({ - id: index.toString(), - link: linkInfo.toString(), - date: dateInfo.toString(), - max_q2a: q2aVersion.toString() - }); - } else { - this.themeList.push({ - id: index.toString(), - link: linkInfo.toString(), - date: dateInfo.toString(), - max_q2a: q2aVersion.toString() - }); - } + if(listType === 'plugins') { + this.pluginList.push({ + id: index.toString(), + link: linkInfo.toString(), + date: dateInfo.toString(), + max_q2a: q2aVersion.toString() + }); + } else { + this.themeList.push({ + id: index.toString(), + link: linkInfo.toString(), + date: dateInfo.toString(), + max_q2a: q2aVersion.toString() + }); + } + } + + // Store Repositories locally + savePluginsList() { + localStorage.setItem('q2adocs_gitHub_plugins', JSON.stringify(this.pluginList)); + } + + saveThemesList() { + localStorage.setItem('q2adocs_gitHub_themes', JSON.stringify(this.themeList)); } - - // Store Repositories locally - savePluginsList() { - localStorage.setItem('q2adocs_gitHub_plugins', JSON.stringify(this.pluginList)); - } - - saveThemesList() { - localStorage.setItem('q2adocs_gitHub_themes', JSON.stringify(this.themeList)); - } } // -------------------------------- @@ -46,16 +46,16 @@ const hasParent = (element, ...parents) => parents.some((parent) => parent.inclu // Loop add class const loopAddClass = (targetClass, addClass) => { - document.querySelectorAll(targetClass).forEach(element => { - element.classList.add(addClass); - }); + document.querySelectorAll(targetClass).forEach(element => { + element.classList.add(addClass); + }); } // Loop remove class const loopRemoveClass = (targetClass, removeClass) => { - document.querySelectorAll(targetClass).forEach(element => { - element.classList.remove(removeClass); - }); + document.querySelectorAll(targetClass).forEach(element => { + element.classList.remove(removeClass); + }); } // Scroll to Elements @@ -71,14 +71,14 @@ scrollToElement = (element) => { // Add current page "selected-nav" class, for the Mega Menu "Documentation" Link item if (document.querySelectorAll('.nav-main .selected-nav').length > 0) { - document.querySelector('.mega-menu-trigger .nav-item').classList.add('selected-nav'); + document.querySelector('.mega-menu-trigger .nav-item').classList.add('selected-nav'); } // Add selected nav to the top level parent (second nav) if ( - document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && - !document.body.classList.contains('template-contribute') && - !document.body.classList.contains('template-addons')) { - document.querySelector('.nav-main-second .selected-nav').closest('.sub-nav').previousElementSibling.classList.add('selected-nav'); + document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && + !document.body.classList.contains('template-contribute') && + !document.body.classList.contains('template-addons')) { + document.querySelector('.nav-main-second .selected-nav').closest('.sub-nav').previousElementSibling.classList.add('selected-nav'); } // toggle menu children @@ -86,18 +86,18 @@ let opened = null; const toggleVisibility = e => e.classList.toggle('display-none'); const handleDropdown = e => { - const clickedItem = e.parentElement.lastChild.previousSibling; - - toggleVisibility(clickedItem); - - if (!opened) { - opened = clickedItem; - } else if (opened == clickedItem) { - opened = null; - } else { - toggleVisibility(opened); - opened = clickedItem; - } + const clickedItem = e.parentElement.lastChild.previousSibling; + + toggleVisibility(clickedItem); + + if (!opened) { + opened = clickedItem; + } else if (opened == clickedItem) { + opened = null; + } else { + toggleVisibility(opened); + opened = clickedItem; + } } const navContainer = document.querySelector('.nav-container'); @@ -107,77 +107,77 @@ const noticeContainer = document.querySelector('.notice-container'); // Handle Click const handleClick = e => { - - // Menu Trigger - if (e.target.id === 'main-nav-trigger') { - if (navContainer.classList.contains('active')) { - navContainer.classList.remove('active'); - backgroundSheet.classList.remove('active'); - } else { - navContainer.classList.add('active'); - backgroundSheet.classList.add('active'); - } - } - - // Mega Menu - if (e.target.parentElement.className.includes('mega-menu-trigger')) { - navMain.classList.toggle('display-none'); - } else if (!hasParent(e.target, '.nav-main')) { - navMain.classList.add('display-none'); - } - - // Nav Menu Children - if (e.target.className.includes('toggleChildren')) { - handleDropdown(e.target); - } else if (opened) { - toggleVisibility(opened); - opened = null; - } - - // Scroll to section on Sidebar navigation click - if (e.target.className.includes('docs-nav-item')) { - const targetDataId = e.target.getAttribute('data-attr-scroll'); - scrollToElement(document.getElementById(targetDataId)); - } - - // Go Top - if (e.target.className.includes('jump-top')) { - window.scrollTo({top: 0, behavior: 'smooth'}); - } - - // Close dark sheet - if (e.target.className.includes('darkPane')) { - if (backgroundSheet.classList.contains('active')) { - navContainer.classList.remove('active'); - backgroundSheet.classList.remove('active'); - } - } - - // Needs Page refresh after fetching - if (e.target.className.includes('close-page-status')) { - location.reload(); - } - - // Notice - if (e.target.className.includes('close-notice') || e.target.className.includes('close-sheet')) { - noticeContainer.classList.add('display-none'); - // set Notice localStorage - localStorage.q2adocs_notice = 'closed'; - } - + + // Menu Trigger + if (e.target.id === 'main-nav-trigger') { + if (navContainer.classList.contains('active')) { + navContainer.classList.remove('active'); + backgroundSheet.classList.remove('active'); + } else { + navContainer.classList.add('active'); + backgroundSheet.classList.add('active'); + } + } + + // Mega Menu + if (e.target.parentElement.className.includes('mega-menu-trigger')) { + navMain.classList.toggle('display-none'); + } else if (!hasParent(e.target, '.nav-main')) { + navMain.classList.add('display-none'); + } + + // Nav Menu Children + if (e.target.className.includes('toggleChildren')) { + handleDropdown(e.target); + } else if (opened) { + toggleVisibility(opened); + opened = null; + } + + // Scroll to section on Sidebar navigation click + if (e.target.className.includes('docs-nav-item')) { + const targetDataId = e.target.getAttribute('data-attr-scroll'); + scrollToElement(document.getElementById(targetDataId)); + } + + // Go Top + if (e.target.className.includes('jump-top')) { + window.scrollTo({top: 0, behavior: 'smooth'}); + } + + // Close dark sheet + if (e.target.className.includes('darkPane')) { + if (backgroundSheet.classList.contains('active')) { + navContainer.classList.remove('active'); + backgroundSheet.classList.remove('active'); + } + } + + // Needs Page refresh after fetching + if (e.target.className.includes('close-page-status')) { + location.reload(); + } + + // Notice + if (e.target.className.includes('close-notice') || e.target.className.includes('close-sheet')) { + noticeContainer.classList.add('display-none'); + // set Notice localStorage + localStorage.q2adocs_notice = 'closed'; + } + } // End handleClick() document.addEventListener('click', handleClick); // Show / Hide Notice on the front page if (localStorage.getItem('q2adocs_notice') === null && noticeContainer != null) { - noticeContainer.classList.remove('display-none'); + noticeContainer.classList.remove('display-none'); } // Quick fix to close Mega Menu, when clicking secondary nav items document.querySelectorAll('.nav-main-second .toggleChildren').forEach(element => { - element.addEventListener('click', (e) => { - navMain.classList.add('display-none'); - }); + element.addEventListener('click', (e) => { + navMain.classList.add('display-none'); + }); }); @@ -190,32 +190,32 @@ const jumpTop = document.getElementById('jump-top'); // Handle Scroll const handleScroll = (e) => { - - // Sticky Topbar - if (window.scrollY > stickyPos) { - header.classList.add('sticky'); - } else { - header.classList.remove('sticky'); - } - - if(window.screen.width > 900) { - // Get the offset position of the scroll-to-top button - const stickyJumpPos = 700; - - const jtRect = jumpTopContainer.getBoundingClientRect().top; - const jtOH = ( window.pageYOffset || jumpTopContainer.scrollTop ) - ( jumpTopContainer.clientTop || 0 ); - const winHeight = window.innerHeight - jumpTopContainer.offsetHeight; - const stopStickyJump = (jtRect + jtOH - winHeight); - - if (window.scrollY > stickyJumpPos && window.scrollY < stopStickyJump) { - jumpTop.classList.add ('active'); - } else if (window.scrollY > stopStickyJump) { - jumpTop.classList.remove('active'); - } else { - jumpTop.classList.remove('active'); - } - } - + + // Sticky Topbar + if (window.scrollY > stickyPos) { + header.classList.add('sticky'); + } else { + header.classList.remove('sticky'); + } + + if(window.screen.width > 900) { + // Get the offset position of the scroll-to-top button + const stickyJumpPos = 700; + + const jtRect = jumpTopContainer.getBoundingClientRect().top; + const jtOH = ( window.pageYOffset || jumpTopContainer.scrollTop ) - ( jumpTopContainer.clientTop || 0 ); + const winHeight = window.innerHeight - jumpTopContainer.offsetHeight; + const stopStickyJump = (jtRect + jtOH - winHeight); + + if (window.scrollY > stickyJumpPos && window.scrollY < stopStickyJump) { + jumpTop.classList.add ('active'); + } else if (window.scrollY > stopStickyJump) { + jumpTop.classList.remove('active'); + } else { + jumpTop.classList.remove('active'); + } + } + } handleScroll(); window.addEventListener('scroll', handleScroll); @@ -227,15 +227,15 @@ window.addEventListener('scroll', handleScroll); // Docs Navigation const articleHeaders = document.querySelectorAll('\ - .page-content h1, .page-content h2, .page-content h3, .page-content h4, .page-content h5, .page-content h6\ + .page-content h1, .page-content h2, .page-content h3, .page-content h4, .page-content h5, .page-content h6\ '); const docsNav = document.querySelector('.docs-nav'); articleHeaders.forEach(element => { - element.classList.add('sectionTitle'); - let dataId = element.getAttribute('id'); - const html = '
  • ' + element.innerHTML +'
  • '; - docsNav.insertAdjacentHTML('beforeend', html); + element.classList.add('sectionTitle'); + let dataId = element.getAttribute('id'); + const html = '
  • ' + element.innerHTML +'
  • '; + docsNav.insertAdjacentHTML('beforeend', html); }); @@ -244,324 +244,324 @@ const docsSection = document.querySelectorAll('.sectionTitle'); function sidebarLinkNav() { if(!document.body.classList.contains('template-homepage') && sidebarDocslinks != null && docsSection != null) { - let index = docsSection.length; + let index = docsSection.length; - while (--index && window.scrollY + 200 < docsSection[index].offsetTop) {} + while (--index && window.scrollY + 200 < docsSection[index].offsetTop) {} - // add the active class if within visible height of the element - if (scrollY - docsSection[index].offsetHeight < docsSection[index].offsetTop) { - sidebarDocslinks.forEach((link) => link.classList.remove('active')); - sidebarDocslinks[index].classList.add('active'); - } - } + // add the active class if within visible height of the element + if (scrollY - docsSection[index].offsetHeight < docsSection[index].offsetTop) { + sidebarDocslinks.forEach((link) => link.classList.remove('active')); + sidebarDocslinks[index].classList.add('active'); + } + } } sidebarLinkNav(); window.addEventListener('scroll', sidebarLinkNav); const currentDate = () => { - const today = new Date(); - const yyyy = today.getFullYear(); - const mm = today.getMonth() + 1; // Months start at 0! - const dd = today.getDate(); - - return new Array(yyyy, mm, dd); + const today = new Date(); + const yyyy = today.getFullYear(); + const mm = today.getMonth() + 1; // Months start at 0! + const dd = today.getDate(); + + return new Array(yyyy, mm, dd); } // Takes one date parameter, calculates and returns the amount of days between present day const calcDays = param => { - const todayDate = currentDate(); - const currentYear = new Date(`${todayDate[0]}/${todayDate[1]}/${todayDate[2]}`); - - let convertDate = param; - if (convertDate.indexOf('-') > -1) { - const arr = convertDate.split('-'); - convertDate = `${arr[0]}/${arr[1]}/${arr[2]}`; - } - - const repositoryYear = new Date(convertDate); - - const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds - const diffDays = Math.round(Math.abs((currentYear - repositoryYear) / oneDay)); - - return diffDays; + const todayDate = currentDate(); + const currentYear = new Date(`${todayDate[0]}/${todayDate[1]}/${todayDate[2]}`); + + let convertDate = param; + if (convertDate.indexOf('-') > -1) { + const arr = convertDate.split('-'); + convertDate = `${arr[0]}/${arr[1]}/${arr[2]}`; + } + + const repositoryYear = new Date(convertDate); + + const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds + const diffDays = Math.round(Math.abs((currentYear - repositoryYear) / oneDay)); + + return diffDays; } // Calculate year gap for Github repositories const calcYears = param => { - const diffYears = calcDays(param); - let yearGap = 0; - - if ( diffYears <= 365) { - yearGap = 1; - } else if ( diffYears > 365 && diffYears <= 730) { - yearGap = 2; - } else if ( diffYears > 730 && diffYears <= 1095) { - yearGap = 3; - } else if ( diffYears > 1095 && diffYears <= 1460) { - yearGap = 4; - } else if ( diffYears > 1460) { - yearGap = 5; - } - return yearGap; + const diffYears = calcDays(param); + let yearGap = 0; + + if ( diffYears <= 365) { + yearGap = 1; + } else if ( diffYears > 365 && diffYears <= 730) { + yearGap = 2; + } else if ( diffYears > 730 && diffYears <= 1095) { + yearGap = 3; + } else if ( diffYears > 1095 && diffYears <= 1460) { + yearGap = 4; + } else if ( diffYears > 1460) { + yearGap = 5; + } + return yearGap; } // Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) const gitLinks = document.querySelectorAll('\ - .template-addons-plugins .page-content li a[href*="https://github.com/"],\ - .template-addons-themes .page-content li a[href*="https://github.com/"]\ + .template-addons-plugins .page-content li a[href*="https://github.com/"],\ + .template-addons-themes .page-content li a[href*="https://github.com/"]\ '); const pluginLinks = document.querySelectorAll('.template-addons-plugins .page-content li a[href*="https://github.com/"]'); const themeLinks = document.querySelectorAll('.template-addons-themes .page-content li a[href*="https://github.com/"]'); if(gitLinks != null && gitLinks.length) { - - const pluginsList = new githubList(); - const themesList = new githubList(); - const isPluginsPage = (document.querySelector('.template-addons-plugins') != null ? true : false); - const isThemesPage = (document.querySelector('.template-addons-themes') != null ? true : false); - - // Set outside Fetch, because it will be reused for other functions - const githubDomain = 'https://github.com/'; - - // Fetch Links - const fetchLinks = () => { - - // Get Q2A version - let q2aVersion; - const getQ2aVersion = 'https://raw.githubusercontent.com/q2a/question2answer/master/VERSION.txt'; - let rawFile = new XMLHttpRequest(); - rawFile.open('GET', getQ2aVersion, false); - rawFile.onreadystatechange = function () - { - if(rawFile.readyState === 4) - { - if(rawFile.status === 200 || rawFile.status == 0) - { - q2aVersion = rawFile.responseText; - console.log(allText); - } - } - } - rawFile.send(null); - - // Set list headers - if(isPluginsPage) { - pluginsList.addListLink('plugins', '1000', 'List_length', pluginLinks.length, q2aVersion); - pluginsList.addListLink('plugins', '1001', 'updated', new Date(), q2aVersion); - } else if (isThemesPage) { - themesList.addListLink('themes', '1000', 'List_length', themeLinks.length, q2aVersion); - themesList.addListLink('themes', '1001', 'updated', new Date(), q2aVersion); - } - - // Create list - for(let i=0; i= 4; - - fetch(githubJSON) - .then(res => res.json()) - .then(jsonResponse => { - if(isRepository) { - // Add link to list - const getQ2aVersion = (jsonResponse.max_q2a != null) ? jsonResponse.max_q2a : '0'; - - if(isPluginsPage) { - pluginsList.addListLink('plugins', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); - } else if (isThemesPage) { - themesList.addListLink('themes', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); - } - } - }) - .catch(error => { - console.log(error) - // Save Unknowns as well. Prevents null Objects. - // We can remove the display tag in the createPluginTags() functions instead, by removing the "else" statement. - if(isRepository) { - if(isPluginsPage) { - pluginsList.addListLink('plugins', [i], gitLinks[i], 'unknown', '0'); - } else if (isThemesPage) { - themesList.addListLink('themes', [i], gitLinks[i], 'unknown', '0'); - } - } - }) - .finally(result => { - if(isPluginsPage) { - pluginsList.savePluginsList(); - } else { - themesList.saveThemesList(); - } - }); - - } // End of for loop - - setTimeout(function(){ - document.querySelector('.page-status-container').innerHTML = '\ -
    \ -
    \ - There has been an update to this page.\ - Please reload.\ -
    \ - refresh\ -
    \ - '; - }, 1500); - } - - // Get saved data from LocalStorage - const retrievedPlugins = JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins')); - const retrievedThemes = JSON.parse(localStorage.getItem('q2adocs_gitHub_themes')); - - // Retrieve single data - const singleKey = (storage) => { - return Object.keys(storage); - } - const singleValue = (storage) => { - return Object.values(storage); - } - - // Change Emoji colors for actual color element - const colorLegends = document.querySelector('.template-addons-plugins blockquote, .template-addons-themes blockquote'); - const colorLegendsUpdated = colorLegends.innerHTML.replace( - '🟢','' - ).replace( - '🔵','' - ).replace( - '🟡','' - ).replace( - '🔴','' - ).replace( - '🔘','' - ); - colorLegends.innerHTML = colorLegendsUpdated; - - // Create tags for both - Plugins and Themes - const createTags = (param) => { - - // get stored current Q2A version - const currentQ2aVersion = Object.values(param[0] || {} )[3]; - - param.forEach((item, index) => { - - const list = param.slice(2); // Remove list header (metadata) indexes - const id = Object.values(list[index] || {} )[0]; - const link = Object.values(list[index] || {} )[1]; - const date = Object.values(list[index] || {} )[2]; - const max_q2a = Object.values(list[index] || {} )[3]; - - // console.log(`${id} === ${link} === ${date}`); - - if(gitLinks[id].parentElement != null && gitLinks[id].parentElement.innerHTML.includes('➔')){ - gitLinks[id].closest('li').classList.add('child-repository'); - } - - if(id != null && link != null && date != null && max_q2a != null) { - // Preppend based on stored id/index, because DOM link order may not be accurate when looping - // index order will be updated when information is fetched again - - const yearGapClass = 'rep-date-' + calcYears(date); - - if (date != 'unknown') { - // if 'max_q2a' key is available - if(max_q2a != '0') { - gitLinks[id].parentElement.insertAdjacentHTML( - 'beforeend', - ` - - Last updated: - - ${date} - - - - Tested with ${max_q2a} - - `, - ); - } else { - gitLinks[id].parentElement.insertAdjacentHTML( - 'beforeend', - ` - - Last updated: - - ${date} - - `, - ); - } - } else if (date == 'unknown') { - // If response is 'bad request' or '404', show "unknown" tag - gitLinks[id].parentElement.insertAdjacentHTML( - 'beforeend', - ` - - Last updated: - Unknown - - `, - ); - } - - } - }); - // console.log(JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins'))); - } - - // Start at zero, in case not fetched yet - let pluginsListUpdated = currentDate(); - let pluginListLength = 0; - - let themesListUpdated = currentDate(); - let themeListLength = 0; - - if(localStorage.q2adocs_gitHub_plugins) { - // Get saved list length for Plugins - pluginsListUpdated = singleValue(retrievedPlugins[1])[2]; - pluginsListUpdated = new Date(pluginsListUpdated).toISOString().split('T')[0]; - pluginListLength = singleValue(retrievedPlugins[0])[2]; - } - if(localStorage.q2adocs_gitHub_themes) { - // Get saved list length for Themes - themesListUpdated = singleValue(retrievedThemes[1])[2]; - themesListUpdated = new Date(themesListUpdated).toISOString().split('T')[0]; - themeListLength = singleValue(retrievedThemes[0])[2]; - } - - // ---------------------------- - // Create the tags / badges --- - // ---------------------------- - - // Test remaining days - // console.log('Days passed since Plugins list updated: ' + calcDays(pluginsListUpdated)); - // console.log('Days passed since Themes list updated: ' + calcDays(themesListUpdated)); - const generateTags = () => { - if(isPluginsPage) { - // Calculate number of days until next fetch - // if "N" days have passed, or the number of links no longer matches the number of saved links, request Fetch - if(retrievedPlugins === null || calcDays(pluginsListUpdated) >= daysUntilNextFetch || pluginLinks.length != pluginListLength) { - fetchLinks(); - } - createTags(retrievedPlugins); - // console.log('retrieved Plugins Object: ', retrievedPlugins); - // console.log('github plugin links length: '+ pluginLinks.length +'; saved: ' + pluginListLength); - } else if (isThemesPage) { - if(retrievedThemes === null || calcDays(themesListUpdated) >= daysUntilNextFetch || themeLinks.length != themeListLength) { - fetchLinks(); - } - createTags(retrievedThemes); - // console.log('retrieved Themes Object: ', retrievedThemes); - // console.log('github theme links length: '+ themeLinks.length +'; saved: ' + themeListLength); - } - } - - generateTags(); - + + const pluginsList = new githubList(); + const themesList = new githubList(); + const isPluginsPage = (document.querySelector('.template-addons-plugins') != null ? true : false); + const isThemesPage = (document.querySelector('.template-addons-themes') != null ? true : false); + + // Set outside Fetch, because it will be reused for other functions + const githubDomain = 'https://github.com/'; + + // Fetch Links + const fetchLinks = () => { + + // Get Q2A version + let q2aVersion; + const getQ2aVersion = 'https://raw.githubusercontent.com/q2a/question2answer/master/VERSION.txt'; + let rawFile = new XMLHttpRequest(); + rawFile.open('GET', getQ2aVersion, false); + rawFile.onreadystatechange = function () + { + if(rawFile.readyState === 4) + { + if(rawFile.status === 200 || rawFile.status == 0) + { + q2aVersion = rawFile.responseText; + console.log(allText); + } + } + } + rawFile.send(null); + + // Set list headers + if(isPluginsPage) { + pluginsList.addListLink('plugins', '1000', 'List_length', pluginLinks.length, q2aVersion); + pluginsList.addListLink('plugins', '1001', 'updated', new Date(), q2aVersion); + } else if (isThemesPage) { + themesList.addListLink('themes', '1000', 'List_length', themeLinks.length, q2aVersion); + themesList.addListLink('themes', '1001', 'updated', new Date(), q2aVersion); + } + + // Create list + for(let i=0; i= 4; + + fetch(githubJSON) + .then(res => res.json()) + .then(jsonResponse => { + if(isRepository) { + // Add link to list + const getQ2aVersion = (jsonResponse.max_q2a != null) ? jsonResponse.max_q2a : '0'; + + if(isPluginsPage) { + pluginsList.addListLink('plugins', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); + } else if (isThemesPage) { + themesList.addListLink('themes', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); + } + } + }) + .catch(error => { + console.log(error) + // Save Unknowns as well. Prevents null Objects. + // We can remove the display tag in the createPluginTags() functions instead, by removing the "else" statement. + if(isRepository) { + if(isPluginsPage) { + pluginsList.addListLink('plugins', [i], gitLinks[i], 'unknown', '0'); + } else if (isThemesPage) { + themesList.addListLink('themes', [i], gitLinks[i], 'unknown', '0'); + } + } + }) + .finally(result => { + if(isPluginsPage) { + pluginsList.savePluginsList(); + } else { + themesList.saveThemesList(); + } + }); + + } // End of for loop + + setTimeout(function() { + document.querySelector('.page-status-container').innerHTML = '\ +
    \ +
    \ + There has been an update to this page.\ + Please reload.\ +
    \ + refresh\ +
    \ + '; + }, 1500); + } + + // Get saved data from LocalStorage + const retrievedPlugins = JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins')); + const retrievedThemes = JSON.parse(localStorage.getItem('q2adocs_gitHub_themes')); + + // Retrieve single data + const singleKey = (storage) => { + return Object.keys(storage); + } + const singleValue = (storage) => { + return Object.values(storage); + } + + // Change Emoji colors for actual color element + const colorLegends = document.querySelector('.template-addons-plugins blockquote, .template-addons-themes blockquote'); + const colorLegendsUpdated = colorLegends.innerHTML.replace( + '🟢','' + ).replace( + '🔵','' + ).replace( + '🟡','' + ).replace( + '🔴','' + ).replace( + '🔘','' + ); + colorLegends.innerHTML = colorLegendsUpdated; + + // Create tags for both - Plugins and Themes + const createTags = (param) => { + + // get stored current Q2A version + const currentQ2aVersion = Object.values(param[0] || {} )[3]; + + param.forEach((item, index) => { + + const list = param.slice(2); // Remove list header (metadata) indexes + const id = Object.values(list[index] || {} )[0]; + const link = Object.values(list[index] || {} )[1]; + const date = Object.values(list[index] || {} )[2]; + const max_q2a = Object.values(list[index] || {} )[3]; + + // console.log(`${id} === ${link} === ${date}`); + + if(gitLinks[id].parentElement != null && gitLinks[id].parentElement.innerHTML.includes('➔')){ + gitLinks[id].closest('li').classList.add('child-repository'); + } + + if(id != null && link != null && date != null && max_q2a != null) { + // Preppend based on stored id/index, because DOM link order may not be accurate when looping + // index order will be updated when information is fetched again + + const yearGapClass = 'rep-date-' + calcYears(date); + + if (date != 'unknown') { + // if 'max_q2a' key is available + if(max_q2a != '0') { + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + + ${date} + + + + Tested with ${max_q2a} + + `, + ); + } else { + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + + ${date} + + `, + ); + } + } else if (date == 'unknown') { + // If response is 'bad request' or '404', show "unknown" tag + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + Unknown + + `, + ); + } + + } + }); + // console.log(JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins'))); + } + + // Start at zero, in case not fetched yet + let pluginsListUpdated = currentDate(); + let pluginListLength = 0; + + let themesListUpdated = currentDate(); + let themeListLength = 0; + + if(localStorage.q2adocs_gitHub_plugins) { + // Get saved list length for Plugins + pluginsListUpdated = singleValue(retrievedPlugins[1])[2]; + pluginsListUpdated = new Date(pluginsListUpdated).toISOString().split('T')[0]; + pluginListLength = singleValue(retrievedPlugins[0])[2]; + } + if(localStorage.q2adocs_gitHub_themes) { + // Get saved list length for Themes + themesListUpdated = singleValue(retrievedThemes[1])[2]; + themesListUpdated = new Date(themesListUpdated).toISOString().split('T')[0]; + themeListLength = singleValue(retrievedThemes[0])[2]; + } + + // ---------------------------- + // Create the tags / badges --- + // ---------------------------- + + // Test remaining days + // console.log('Days passed since Plugins list updated: ' + calcDays(pluginsListUpdated)); + // console.log('Days passed since Themes list updated: ' + calcDays(themesListUpdated)); + const generateTags = () => { + if(isPluginsPage) { + // Calculate number of days until next fetch + // if "N" days have passed, or the number of links no longer matches the number of saved links, request Fetch + if(retrievedPlugins === null || calcDays(pluginsListUpdated) >= daysUntilNextFetch || pluginLinks.length != pluginListLength) { + fetchLinks(); + } + createTags(retrievedPlugins); + // console.log('retrieved Plugins Object: ', retrievedPlugins); + // console.log('github plugin links length: '+ pluginLinks.length +'; saved: ' + pluginListLength); + } else if (isThemesPage) { + if(retrievedThemes === null || calcDays(themesListUpdated) >= daysUntilNextFetch || themeLinks.length != themeListLength) { + fetchLinks(); + } + createTags(retrievedThemes); + // console.log('retrieved Themes Object: ', retrievedThemes); + // console.log('github theme links length: '+ themeLinks.length +'; saved: ' + themeListLength); + } + } + + generateTags(); + } // End of if gitLinks.length From d3d967008aec35d046de17b92c11ed7134a68f4e Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Fri, 29 Nov 2024 02:35:58 +0000 Subject: [PATCH 24/29] Better Phrasing --- js/scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/scripts.js b/js/scripts.js index f3e220d..50585c5 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -404,7 +404,7 @@ if(gitLinks != null && gitLinks.length) { document.querySelector('.page-status-container').innerHTML = '\
    \
    \ - There has been an update to this page.\ + This page has been updated.\ Please reload.\
    \ refresh\ From d455d610b6c3db0fa9b573dd237c5b7d59272c1c Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 7 Dec 2024 08:24:32 +0000 Subject: [PATCH 25/29] Seperated GithubLinks script, from main scripts, since they're only used for 2 pages - Seperated GithubLinks script, from main scripts, since they're only used for 2 pages. - Reverted Gemfile.lock to original. The extra Windows systems options weren't necessary. --- Gemfile.lock | 4 - _layouts/default.html | 5 + addons/plugins.md | 2 +- js/GithubLinks.js | 348 ++++++++++++++++++++++++++++++++++++++++ js/scripts.js | 363 ++---------------------------------------- 5 files changed, 363 insertions(+), 359 deletions(-) create mode 100644 js/GithubLinks.js diff --git a/Gemfile.lock b/Gemfile.lock index 7216d23..0256451 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,7 +52,6 @@ GEM faraday-net_http (3.3.0) net-http ffi (1.17.0) - ffi (1.17.0-x64-mingw-ucrt) fiber-annotation (0.2.0) fiber-local (1.1.0) fiber-storage @@ -256,8 +255,6 @@ GEM minitest (5.25.1) net-http (0.4.1) uri - nokogiri (1.16.7-x64-mingw-ucrt) - racc (~> 1.4) nokogiri (1.16.7-x86_64-darwin) racc (~> 1.4) nokogiri (1.16.7-x86_64-linux) @@ -309,7 +306,6 @@ GEM zeitwerk (2.6.18) PLATFORMS - x64-mingw-ucrt x86_64-darwin-22 x86_64-linux diff --git a/_layouts/default.html b/_layouts/default.html index 5f4b888..ec4d13d 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -30,5 +30,10 @@ {% include footer.html %} + + {% if (page.slug == 'addons-plugins') or (page.slug == 'addons-themes') %} + + {% endif %} + diff --git a/addons/plugins.md b/addons/plugins.md index 229e545..93bb56c 100644 --- a/addons/plugins.md +++ b/addons/plugins.md @@ -30,7 +30,7 @@ To install a plugin (requires Q2A 1.3 or later), place its directory in Q2A's `q - [Express Editor](https://github.com/amiyasahu/q2a-express-editor) by [Amiya Sahu](http://amiyasahu.com/). Recent version of [CKEditor](http://ckeditor.com/) bundled with [Ace Editor](http://ace.c9.io/) for smooth code editing. - [Lightbox-images](https://github.com/ProThoughts/q2apro-lightbox-images) by [q2apro.com](https://github.com/q2apro). Provides a Lightbox Effect for all images in posts. - [Markdown Editor](https://github.com/svivian/q2a-markdown-editor) by [Scott Vivian](http://codelair.co.uk/) of [PokéBase](http://pokemondb.net/pokebase/). This plugin uses [Markdown](https://www.markdownguide.org/getting-started/), a lightweight markup language for creating formatted text using a plain-text editor. For example `**bold**` for bold text, or start a line with `>` for quoting sources. -- ➔ [Markdown Editor with TOAST UI](https://github.com/ProThoughts/markdown-editor-for-q2a) by **awei922**. This version is based off of **Scott Vivian**'s version & [tui.editor](https://ui.toast.com/tui-editor), and allows you to edit your Markdown documents using text or WYSIWYG. Screenshot [here](https://raw.githubusercontent.com/ProThoughts/markdown-editor-for-q2a/refs/heads/master/screenshot.png). +- ➔ [Markdown Editor with TOAST UI](https://github.com/ProThoughts/markdown-editor-for-q2a) by **awei922**. This version is based off of **Scott Vivian**'s version + [tui.editor](https://ui.toast.com/tui-editor), and allows you to edit your Markdown documents using text or WYSIWYG. Screenshot [here](https://raw.githubusercontent.com/ProThoughts/markdown-editor-for-q2a/refs/heads/master/screenshot.png). - ➔ **awei922**'s version of **Markdown with TUI** also [forked here](https://github.com/fardina/markdown-editor-for-q2a) by [Fardina](https://github.com/fardina) for Q2A 1.8.6. - [Memes for Text](https://github.com/thibaultduponchelle/q2a-memes) by [Thibault Duponchelle](https://github.com/thibaultduponchelle). Post *lolfaces* in your answers or comments. - [Miranda](http://www.question2answer.org/qa/51849/tinymcewrapper-miranda-updated-most-powerful-editor-joins) [Tinymcse Wrapper](https://www.tinymce.com/) by Don Shakespeare. Adds Tinymcse to be used across posts as well as other text areas like messages, wall posts and is higly customizable. diff --git a/js/GithubLinks.js b/js/GithubLinks.js new file mode 100644 index 0000000..cf05925 --- /dev/null +++ b/js/GithubLinks.js @@ -0,0 +1,348 @@ + +// Days until next fetch for Github repository links +const daysUntilNextFetch = 7; + +class githubList { + constructor() { + this.pluginList = []; + this.themeList = []; + } + + addListLink(listType, index, linkInfo, dateInfo, q2aVersion) { + // Push object to array, that later will be saved locally + if(listType === 'plugins') { + this.pluginList.push({ + id: index.toString(), + link: linkInfo.toString(), + date: dateInfo.toString(), + max_q2a: q2aVersion.toString() + }); + } else { + this.themeList.push({ + id: index.toString(), + link: linkInfo.toString(), + date: dateInfo.toString(), + max_q2a: q2aVersion.toString() + }); + } + } + + // Store Repositories locally + savePluginsList() { + localStorage.setItem('q2adocs_gitHub_plugins', JSON.stringify(this.pluginList)); + } + saveThemesList() { + localStorage.setItem('q2adocs_gitHub_themes', JSON.stringify(this.themeList)); + } + + getList(listType) { + // List param, will contain either: q2adocs_gitHub_plugins or q2adocs_gitHub_themes + return JSON.parse(localStorage.getItem(listType)); + } + +} + +const currentDate = () => { + const today = new Date(); + const yyyy = today.getFullYear(); + const mm = today.getMonth() + 1; // Months start at 0! + const dd = today.getDate(); + + return new Array(yyyy, mm, dd); +} + +// Takes one date parameter, calculates and returns the amount of days between present day +const calcDays = param => { + const todayDate = currentDate(); + const currentYear = new Date(`${todayDate[0]}/${todayDate[1]}/${todayDate[2]}`); + + let convertDate = param; + if (convertDate.indexOf('-') > -1) { + const arr = convertDate.split('-'); + convertDate = `${arr[0]}/${arr[1]}/${arr[2]}`; + } + + const repositoryYear = new Date(convertDate); + + const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds + const diffDays = Math.round(Math.abs((currentYear - repositoryYear) / oneDay)); + + return diffDays; +} + +// Calculate year gap for Github repositories +const calcYears = param => { + const diffYears = calcDays(param); + let yearGap = 0; + + if ( diffYears <= 365) { + yearGap = 1; + } else if ( diffYears > 365 && diffYears <= 730) { + yearGap = 2; + } else if ( diffYears > 730 && diffYears <= 1095) { + yearGap = 3; + } else if ( diffYears > 1095 && diffYears <= 1460) { + yearGap = 4; + } else if ( diffYears > 1460) { + yearGap = 5; + } + return yearGap; +} + +// Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) +const gitLinks = document.querySelectorAll('\ + .template-addons-plugins .page-content li a[href*="https://github.com/"],\ + .template-addons-themes .page-content li a[href*="https://github.com/"]\ +'); + +const pluginLinks = document.querySelectorAll('.template-addons-plugins .page-content li a[href*="https://github.com/"]'); +const themeLinks = document.querySelectorAll('.template-addons-themes .page-content li a[href*="https://github.com/"]'); + +if(gitLinks != null && gitLinks.length) { + + const pluginsList = new githubList(); + const themesList = new githubList(); + const isPluginsPage = (document.querySelector('.template-addons-plugins') != null ? true : false); + const isThemesPage = (document.querySelector('.template-addons-themes') != null ? true : false); + + // Set outside Fetch, because it will be reused for other functions + const githubDomain = 'https://github.com/'; + + // Fetch Links + const fetchLinks = () => { + + // Get Q2A version + let q2aVersion; + const getQ2aVersion = 'https://raw.githubusercontent.com/q2a/question2answer/master/VERSION.txt'; + let rawFile = new XMLHttpRequest(); + rawFile.open('GET', getQ2aVersion, false); + rawFile.onreadystatechange = function () { + if(rawFile.readyState === 4) { + if(rawFile.status === 200 || rawFile.status == 0) { + q2aVersion = rawFile.responseText; + console.log(allText); + } + } + } + rawFile.send(null); + + // Set list headers + if(isPluginsPage) { + pluginsList.addListLink('plugins', '1000', 'List_length', pluginLinks.length, q2aVersion); + pluginsList.addListLink('plugins', '1001', 'updated', new Date(), q2aVersion); + } else if (isThemesPage) { + themesList.addListLink('themes', '1000', 'List_length', themeLinks.length, q2aVersion); + themesList.addListLink('themes', '1001', 'updated', new Date(), q2aVersion); + } + + // Create list + for(let i=0; i= 4; + + fetch(githubJSON) + .then(res => res.json()) + .then(jsonResponse => { + if(isRepository) { + // Add link to list + const getQ2aVersion = (jsonResponse.max_q2a != null) ? jsonResponse.max_q2a : '0'; + + if(isPluginsPage) { + pluginsList.addListLink('plugins', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); + } else if (isThemesPage) { + themesList.addListLink('themes', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); + } + } + }) + .catch(error => { + console.log(error) + // Save Unknowns as well. Prevents null Objects. + // We can remove the display tag in the createPluginTags() functions instead, by removing the "else" statement. + if(isRepository) { + if(isPluginsPage) { + pluginsList.addListLink('plugins', [i], gitLinks[i], 'unknown', '0'); + } else if (isThemesPage) { + themesList.addListLink('themes', [i], gitLinks[i], 'unknown', '0'); + } + } + }) + .finally(result => { + if(isPluginsPage) { + pluginsList.savePluginsList(); + } else { + themesList.saveThemesList(); + } + }); + + } // End of for loop + + setTimeout(function() { + document.querySelector('.page-status-container').innerHTML = '\ +
    \ +
    \ + This page has been updated.\ + Please reload.\ +
    \ + refresh\ +
    \ + '; + }, 1500); + } + + // Get saved data from LocalStorage + const retrievedPlugins = pluginsList.getList('q2adocs_gitHub_plugins'); + const retrievedThemes = themesList.getList('q2adocs_gitHub_themes'); + + // Retrieve single data + const singleKey = (storage) => { + return Object.keys(storage); + } + const singleValue = (storage) => { + return Object.values(storage); + } + + // Change Emoji colors for actual color element + const colorLegends = document.querySelector('.template-addons-plugins blockquote, .template-addons-themes blockquote'); + const colorLegendsUpdated = colorLegends.innerHTML.replace( + '🟢','' + ).replace( + '🔵','' + ).replace( + '🟡','' + ).replace( + '🔴','' + ).replace( + '🔘','' + ); + colorLegends.innerHTML = colorLegendsUpdated; + + // Create tags for both - Plugins and Themes + const createTags = (param) => { + + // get stored current Q2A version + const currentQ2aVersion = Object.values(param[0] || {} )[3]; + + param.forEach((item, index) => { + + const list = param.slice(2); // Remove list header (metadata) indexes + const id = Object.values(list[index] || {} )[0]; + const link = Object.values(list[index] || {} )[1]; + const date = Object.values(list[index] || {} )[2]; + const max_q2a = Object.values(list[index] || {} )[3]; + + // console.log(`${id} === ${link} === ${date}`); + + if(gitLinks[index].parentElement.innerHTML.includes('➔')){ + gitLinks[index].closest('li').classList.add('child-repository'); + } + + if(id != null && link != null && date != null && max_q2a != null) { + // Preppend based on stored id/index, because DOM link order may not be accurate when looping + // index order will be updated when information is fetched again + + const yearGapClass = 'rep-date-' + calcYears(date); + + if (date != 'unknown') { + // if 'max_q2a' key is available + if(max_q2a != '0') { + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + + ${date} + + + + Tested with ${max_q2a} + + `, + ); + } else { + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + + ${date} + + `, + ); + } + } else if (date == 'unknown') { + // If response is 'bad request' or '404', show "unknown" tag + gitLinks[id].parentElement.insertAdjacentHTML( + 'beforeend', + ` + + Last updated: + Unknown + + `, + ); + } + + } + }); + // console.log(JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins'))); + } + + // Start at zero, in case not fetched yet + let pluginsListUpdated = currentDate(); + let pluginListLength = 0; + + let themesListUpdated = currentDate(); + let themeListLength = 0; + + if(localStorage.q2adocs_gitHub_plugins) { + // Get saved list length for Plugins + pluginsListUpdated = singleValue(retrievedPlugins[1])[2]; + pluginsListUpdated = new Date(pluginsListUpdated).toISOString().split('T')[0]; + pluginListLength = singleValue(retrievedPlugins[0])[2]; + } + if(localStorage.q2adocs_gitHub_themes) { + // Get saved list length for Themes + themesListUpdated = singleValue(retrievedThemes[1])[2]; + themesListUpdated = new Date(themesListUpdated).toISOString().split('T')[0]; + themeListLength = singleValue(retrievedThemes[0])[2]; + } + + // ---------------------------- + // Create the tags / badges --- + // ---------------------------- + + // Test remaining days + // console.log('Days passed since Plugins list updated: ' + calcDays(pluginsListUpdated)); + // console.log('Days passed since Themes list updated: ' + calcDays(themesListUpdated)); + const generateTags = () => { + if(isPluginsPage) { + // Calculate number of days until next fetch + // if "N" days have passed, or the number of links no longer matches the number of saved links, request Fetch + if(retrievedPlugins === null || calcDays(pluginsListUpdated) >= daysUntilNextFetch || pluginLinks.length != pluginListLength) { + fetchLinks(); + } + createTags(retrievedPlugins); + // console.log('retrieved Plugins Object: ', retrievedPlugins); + // console.log('github plugin links length: '+ pluginLinks.length +'; saved: ' + pluginListLength); + } else if (isThemesPage) { + if(retrievedThemes === null || calcDays(themesListUpdated) >= daysUntilNextFetch || themeLinks.length != themeListLength) { + fetchLinks(); + } + createTags(retrievedThemes); + // console.log('retrieved Themes Object: ', retrievedThemes); + // console.log('github theme links length: '+ themeLinks.length +'; saved: ' + themeListLength); + } + } + generateTags(); + +} // End of if gitLinks.length + + + diff --git a/js/scripts.js b/js/scripts.js index 50585c5..6d7bc2a 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -1,42 +1,3 @@ - -// Days until next fetch for Github repository links -const daysUntilNextFetch = 7; - -class githubList { - constructor() { - this.pluginList = []; - this.themeList = []; - } - - addListLink(listType, index, linkInfo, dateInfo, q2aVersion) { - // Push object to array, that later will be saved locally - if(listType === 'plugins') { - this.pluginList.push({ - id: index.toString(), - link: linkInfo.toString(), - date: dateInfo.toString(), - max_q2a: q2aVersion.toString() - }); - } else { - this.themeList.push({ - id: index.toString(), - link: linkInfo.toString(), - date: dateInfo.toString(), - max_q2a: q2aVersion.toString() - }); - } - } - - // Store Repositories locally - savePluginsList() { - localStorage.setItem('q2adocs_gitHub_plugins', JSON.stringify(this.pluginList)); - } - - saveThemesList() { - localStorage.setItem('q2adocs_gitHub_themes', JSON.stringify(this.themeList)); - } -} - // -------------------------------- // Common functions, Reusable STUFF // -------------------------------- @@ -59,11 +20,11 @@ const loopRemoveClass = (targetClass, removeClass) => { } // Scroll to Elements -scrollToElement = (element) => { +const scrollToElement = e => { window.scroll({ behavior: 'smooth', left: 0, - top: element.offsetTop - 90 + top: e.offsetTop - 90 }); } @@ -74,10 +35,10 @@ if (document.querySelectorAll('.nav-main .selected-nav').length > 0) { document.querySelector('.mega-menu-trigger .nav-item').classList.add('selected-nav'); } // Add selected nav to the top level parent (second nav) -if ( - document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && - !document.body.classList.contains('template-contribute') && - !document.body.classList.contains('template-addons')) { +if (document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && + !document.body.classList.contains('template-contribute') && + !document.body.classList.contains('template-addons') + ){ document.querySelector('.nav-main-second .selected-nav').closest('.sub-nav').previousElementSibling.classList.add('selected-nav'); } @@ -170,7 +131,7 @@ document.addEventListener('click', handleClick); // Show / Hide Notice on the front page if (localStorage.getItem('q2adocs_notice') === null && noticeContainer != null) { - noticeContainer.classList.remove('display-none'); + noticeContainer.classList.remove('display-none'); } // Quick fix to close Mega Menu, when clicking secondary nav items @@ -189,7 +150,7 @@ const jumpTopContainer = document.querySelector('.jump-top-container'); const jumpTop = document.getElementById('jump-top'); // Handle Scroll -const handleScroll = (e) => { +const handleScroll = e => { // Sticky Topbar if (window.scrollY > stickyPos) { @@ -242,7 +203,7 @@ articleHeaders.forEach(element => { const sidebarDocslinks = document.querySelectorAll('.docs-nav-item'); const docsSection = document.querySelectorAll('.sectionTitle'); -function sidebarLinkNav() { +const sidebarLinkNav = e => { if(!document.body.classList.contains('template-homepage') && sidebarDocslinks != null && docsSection != null) { let index = docsSection.length; @@ -258,311 +219,5 @@ function sidebarLinkNav() { sidebarLinkNav(); window.addEventListener('scroll', sidebarLinkNav); -const currentDate = () => { - const today = new Date(); - const yyyy = today.getFullYear(); - const mm = today.getMonth() + 1; // Months start at 0! - const dd = today.getDate(); - - return new Array(yyyy, mm, dd); -} - -// Takes one date parameter, calculates and returns the amount of days between present day -const calcDays = param => { - const todayDate = currentDate(); - const currentYear = new Date(`${todayDate[0]}/${todayDate[1]}/${todayDate[2]}`); - - let convertDate = param; - if (convertDate.indexOf('-') > -1) { - const arr = convertDate.split('-'); - convertDate = `${arr[0]}/${arr[1]}/${arr[2]}`; - } - - const repositoryYear = new Date(convertDate); - - const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds - const diffDays = Math.round(Math.abs((currentYear - repositoryYear) / oneDay)); - - return diffDays; -} - -// Calculate year gap for Github repositories -const calcYears = param => { - const diffYears = calcDays(param); - let yearGap = 0; - - if ( diffYears <= 365) { - yearGap = 1; - } else if ( diffYears > 365 && diffYears <= 730) { - yearGap = 2; - } else if ( diffYears > 730 && diffYears <= 1095) { - yearGap = 3; - } else if ( diffYears > 1095 && diffYears <= 1460) { - yearGap = 4; - } else if ( diffYears > 1460) { - yearGap = 5; - } - return yearGap; -} - -// Checks for Github repository links, and prepends a tag with their date "year-month-day". (fetched from their metadata.js files) -const gitLinks = document.querySelectorAll('\ - .template-addons-plugins .page-content li a[href*="https://github.com/"],\ - .template-addons-themes .page-content li a[href*="https://github.com/"]\ -'); - -const pluginLinks = document.querySelectorAll('.template-addons-plugins .page-content li a[href*="https://github.com/"]'); -const themeLinks = document.querySelectorAll('.template-addons-themes .page-content li a[href*="https://github.com/"]'); - -if(gitLinks != null && gitLinks.length) { - - const pluginsList = new githubList(); - const themesList = new githubList(); - const isPluginsPage = (document.querySelector('.template-addons-plugins') != null ? true : false); - const isThemesPage = (document.querySelector('.template-addons-themes') != null ? true : false); - - // Set outside Fetch, because it will be reused for other functions - const githubDomain = 'https://github.com/'; - - // Fetch Links - const fetchLinks = () => { - - // Get Q2A version - let q2aVersion; - const getQ2aVersion = 'https://raw.githubusercontent.com/q2a/question2answer/master/VERSION.txt'; - let rawFile = new XMLHttpRequest(); - rawFile.open('GET', getQ2aVersion, false); - rawFile.onreadystatechange = function () - { - if(rawFile.readyState === 4) - { - if(rawFile.status === 200 || rawFile.status == 0) - { - q2aVersion = rawFile.responseText; - console.log(allText); - } - } - } - rawFile.send(null); - - // Set list headers - if(isPluginsPage) { - pluginsList.addListLink('plugins', '1000', 'List_length', pluginLinks.length, q2aVersion); - pluginsList.addListLink('plugins', '1001', 'updated', new Date(), q2aVersion); - } else if (isThemesPage) { - themesList.addListLink('themes', '1000', 'List_length', themeLinks.length, q2aVersion); - themesList.addListLink('themes', '1001', 'updated', new Date(), q2aVersion); - } - - // Create list - for(let i=0; i= 4; - - fetch(githubJSON) - .then(res => res.json()) - .then(jsonResponse => { - if(isRepository) { - // Add link to list - const getQ2aVersion = (jsonResponse.max_q2a != null) ? jsonResponse.max_q2a : '0'; - - if(isPluginsPage) { - pluginsList.addListLink('plugins', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); - } else if (isThemesPage) { - themesList.addListLink('themes', [i], gitLinks[i], jsonResponse.date, getQ2aVersion); - } - } - }) - .catch(error => { - console.log(error) - // Save Unknowns as well. Prevents null Objects. - // We can remove the display tag in the createPluginTags() functions instead, by removing the "else" statement. - if(isRepository) { - if(isPluginsPage) { - pluginsList.addListLink('plugins', [i], gitLinks[i], 'unknown', '0'); - } else if (isThemesPage) { - themesList.addListLink('themes', [i], gitLinks[i], 'unknown', '0'); - } - } - }) - .finally(result => { - if(isPluginsPage) { - pluginsList.savePluginsList(); - } else { - themesList.saveThemesList(); - } - }); - - } // End of for loop - - setTimeout(function() { - document.querySelector('.page-status-container').innerHTML = '\ -
    \ -
    \ - This page has been updated.\ - Please reload.\ -
    \ - refresh\ -
    \ - '; - }, 1500); - } - - // Get saved data from LocalStorage - const retrievedPlugins = JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins')); - const retrievedThemes = JSON.parse(localStorage.getItem('q2adocs_gitHub_themes')); - - // Retrieve single data - const singleKey = (storage) => { - return Object.keys(storage); - } - const singleValue = (storage) => { - return Object.values(storage); - } - - // Change Emoji colors for actual color element - const colorLegends = document.querySelector('.template-addons-plugins blockquote, .template-addons-themes blockquote'); - const colorLegendsUpdated = colorLegends.innerHTML.replace( - '🟢','' - ).replace( - '🔵','' - ).replace( - '🟡','' - ).replace( - '🔴','' - ).replace( - '🔘','' - ); - colorLegends.innerHTML = colorLegendsUpdated; - - // Create tags for both - Plugins and Themes - const createTags = (param) => { - - // get stored current Q2A version - const currentQ2aVersion = Object.values(param[0] || {} )[3]; - - param.forEach((item, index) => { - - const list = param.slice(2); // Remove list header (metadata) indexes - const id = Object.values(list[index] || {} )[0]; - const link = Object.values(list[index] || {} )[1]; - const date = Object.values(list[index] || {} )[2]; - const max_q2a = Object.values(list[index] || {} )[3]; - - // console.log(`${id} === ${link} === ${date}`); - - if(gitLinks[id].parentElement != null && gitLinks[id].parentElement.innerHTML.includes('➔')){ - gitLinks[id].closest('li').classList.add('child-repository'); - } - - if(id != null && link != null && date != null && max_q2a != null) { - // Preppend based on stored id/index, because DOM link order may not be accurate when looping - // index order will be updated when information is fetched again - - const yearGapClass = 'rep-date-' + calcYears(date); - - if (date != 'unknown') { - // if 'max_q2a' key is available - if(max_q2a != '0') { - gitLinks[id].parentElement.insertAdjacentHTML( - 'beforeend', - ` - - Last updated: - - ${date} - - - - Tested with ${max_q2a} - - `, - ); - } else { - gitLinks[id].parentElement.insertAdjacentHTML( - 'beforeend', - ` - - Last updated: - - ${date} - - `, - ); - } - } else if (date == 'unknown') { - // If response is 'bad request' or '404', show "unknown" tag - gitLinks[id].parentElement.insertAdjacentHTML( - 'beforeend', - ` - - Last updated: - Unknown - - `, - ); - } - - } - }); - // console.log(JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins'))); - } - - // Start at zero, in case not fetched yet - let pluginsListUpdated = currentDate(); - let pluginListLength = 0; - - let themesListUpdated = currentDate(); - let themeListLength = 0; - - if(localStorage.q2adocs_gitHub_plugins) { - // Get saved list length for Plugins - pluginsListUpdated = singleValue(retrievedPlugins[1])[2]; - pluginsListUpdated = new Date(pluginsListUpdated).toISOString().split('T')[0]; - pluginListLength = singleValue(retrievedPlugins[0])[2]; - } - if(localStorage.q2adocs_gitHub_themes) { - // Get saved list length for Themes - themesListUpdated = singleValue(retrievedThemes[1])[2]; - themesListUpdated = new Date(themesListUpdated).toISOString().split('T')[0]; - themeListLength = singleValue(retrievedThemes[0])[2]; - } - - // ---------------------------- - // Create the tags / badges --- - // ---------------------------- - - // Test remaining days - // console.log('Days passed since Plugins list updated: ' + calcDays(pluginsListUpdated)); - // console.log('Days passed since Themes list updated: ' + calcDays(themesListUpdated)); - const generateTags = () => { - if(isPluginsPage) { - // Calculate number of days until next fetch - // if "N" days have passed, or the number of links no longer matches the number of saved links, request Fetch - if(retrievedPlugins === null || calcDays(pluginsListUpdated) >= daysUntilNextFetch || pluginLinks.length != pluginListLength) { - fetchLinks(); - } - createTags(retrievedPlugins); - // console.log('retrieved Plugins Object: ', retrievedPlugins); - // console.log('github plugin links length: '+ pluginLinks.length +'; saved: ' + pluginListLength); - } else if (isThemesPage) { - if(retrievedThemes === null || calcDays(themesListUpdated) >= daysUntilNextFetch || themeLinks.length != themeListLength) { - fetchLinks(); - } - createTags(retrievedThemes); - // console.log('retrieved Themes Object: ', retrievedThemes); - // console.log('github theme links length: '+ themeLinks.length +'; saved: ' + themeListLength); - } - } - - generateTags(); - -} // End of if gitLinks.length - From 441f921937c78a9479157c9e9fdbf459e394ba9c Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sat, 7 Dec 2024 09:16:50 +0000 Subject: [PATCH 26/29] Mobile Menu patches --- css/styles.css | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/css/styles.css b/css/styles.css index f24e049..d955d4c 100644 --- a/css/styles.css +++ b/css/styles.css @@ -423,8 +423,7 @@ ul.sub-nav { .header ul.sub-nav { top: 40px; } -.mega-menu .nav-main:before, -.header .nav-main-second ul.sub-nav:before { +.mega-menu .nav-main:before, .header ul.sub-nav:before { content: ''; position: absolute; left: 30px; @@ -1088,6 +1087,27 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { .nav-item .docs-svg { vertical-align: text-bottom; } + ul.sub-nav { + box-shadow: 0 2px 10px 0 rgb(0 0 0 / 30%); + } + .header ul.sub-nav:after { + content: ""; + position: fixed; + top: 50px; + left: 0; + right: 0; + bottom: 0; + z-index: -1; + height: 100vh; + background-color: #fff; + opacity: .5; + } + .sub-nav .sub-nav-item, .sub-nav a.sub-nav-item { + line-height: 24px; + padding: .5rem 1rem .5rem 2rem; + border-radius: 0 !important; + font-weight: 500; + } .jump-top-container { padding-bottom: 2rem; From a58052204b51d753384c9df5851f7c1a172a4c2e Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sun, 8 Dec 2024 08:19:14 +0000 Subject: [PATCH 27/29] Syntax Error fix for _layouts\default, and UX patches --- _layouts/default.html | 2 +- css/styles.css | 9 ++++++++- js/scripts.js | 26 ++++++++++++++++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index ec4d13d..8b8ce47 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -31,7 +31,7 @@ - {% if (page.slug == 'addons-plugins') or (page.slug == 'addons-themes') %} + {% if page.slug == 'addons-plugins' or page.slug == 'addons-themes' %} {% endif %} diff --git a/css/styles.css b/css/styles.css index d955d4c..09c7c07 100644 --- a/css/styles.css +++ b/css/styles.css @@ -235,6 +235,13 @@ blockquote { clear: both; } +.nav-container .nav > li { + transition: opacity .05s linear; +} +.obfuscate { + opacity: .5; +} + /* ================== LAYOUT @@ -1099,7 +1106,7 @@ a[href*="/releases/question2answer"][href$=".zip"]:hover { bottom: 0; z-index: -1; height: 100vh; - background-color: #fff; + /* background-color: #fff; */ opacity: .5; } .sub-nav .sub-nav-item, .sub-nav a.sub-nav-item { diff --git a/js/scripts.js b/js/scripts.js index 6d7bc2a..4a6c505 100644 --- a/js/scripts.js +++ b/js/scripts.js @@ -3,22 +3,32 @@ // -------------------------------- // Check for Parents -const hasParent = (element, ...parents) => parents.some((parent) => parent.includes(element)); +const hasParent = (element, ...parents) => parents.some( parent => parent.includes(element)); // Loop add class const loopAddClass = (targetClass, addClass) => { - document.querySelectorAll(targetClass).forEach(element => { + document.querySelectorAll(targetClass).forEach( element => { element.classList.add(addClass); }); } // Loop remove class const loopRemoveClass = (targetClass, removeClass) => { - document.querySelectorAll(targetClass).forEach(element => { + document.querySelectorAll(targetClass).forEach( element => { element.classList.remove(removeClass); }); } +// Loop toggle class +const loopToggleClass = (targetClass, toggleClass) => { + document.querySelectorAll(targetClass).forEach( element => { + element.classList.toggle(toggleClass); + }); +} + +// Toggle Visibility. +const toggleVisibility = e => e.classList.toggle('display-none'); + // Scroll to Elements const scrollToElement = e => { window.scroll({ @@ -44,12 +54,14 @@ if (document.querySelectorAll('.nav-main-second .selected-nav').length > 0 && // toggle menu children let opened = null; -const toggleVisibility = e => e.classList.toggle('display-none'); - const handleDropdown = e => { const clickedItem = e.parentElement.lastChild.previousSibling; toggleVisibility(clickedItem); + if (window.screen.width < 1024) { + loopAddClass('.nav-container .nav > li', 'obfuscate'); + e.parentElement.classList.remove('obfuscate'); // Remove it for the current List item + } if (!opened) { opened = clickedItem; @@ -82,7 +94,7 @@ const handleClick = e => { // Mega Menu if (e.target.parentElement.className.includes('mega-menu-trigger')) { - navMain.classList.toggle('display-none'); + toggleVisibility(navMain); } else if (!hasParent(e.target, '.nav-main')) { navMain.classList.add('display-none'); } @@ -92,6 +104,8 @@ const handleClick = e => { handleDropdown(e.target); } else if (opened) { toggleVisibility(opened); + if (window.screen.width < 1024) + loopRemoveClass('.nav-container .nav > li', 'obfuscate'); opened = null; } From 9e5c873dced6b95ceb7dea39fd882f4a14c32976 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sun, 8 Dec 2024 08:37:19 +0000 Subject: [PATCH 28/29] Consistent case sensitive --- js/GithubLinks.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/js/GithubLinks.js b/js/GithubLinks.js index cf05925..81f9395 100644 --- a/js/GithubLinks.js +++ b/js/GithubLinks.js @@ -29,14 +29,14 @@ class githubList { // Store Repositories locally savePluginsList() { - localStorage.setItem('q2adocs_gitHub_plugins', JSON.stringify(this.pluginList)); + localStorage.setItem('q2adocs_github_plugins', JSON.stringify(this.pluginList)); } saveThemesList() { - localStorage.setItem('q2adocs_gitHub_themes', JSON.stringify(this.themeList)); + localStorage.setItem('q2adocs_github_themes', JSON.stringify(this.themeList)); } getList(listType) { - // List param, will contain either: q2adocs_gitHub_plugins or q2adocs_gitHub_themes + // List param, will contain either: q2adocs_github_plugins or q2adocs_github_themes return JSON.parse(localStorage.getItem(listType)); } @@ -195,8 +195,8 @@ if(gitLinks != null && gitLinks.length) { } // Get saved data from LocalStorage - const retrievedPlugins = pluginsList.getList('q2adocs_gitHub_plugins'); - const retrievedThemes = themesList.getList('q2adocs_gitHub_themes'); + const retrievedPlugins = pluginsList.getList('q2adocs_github_plugins'); + const retrievedThemes = themesList.getList('q2adocs_github_themes'); // Retrieve single data const singleKey = (storage) => { @@ -291,7 +291,7 @@ if(gitLinks != null && gitLinks.length) { } }); - // console.log(JSON.parse(localStorage.getItem('q2adocs_gitHub_plugins'))); + // console.log(JSON.parse(localStorage.getItem('q2adocs_github_plugins'))); } // Start at zero, in case not fetched yet @@ -301,13 +301,13 @@ if(gitLinks != null && gitLinks.length) { let themesListUpdated = currentDate(); let themeListLength = 0; - if(localStorage.q2adocs_gitHub_plugins) { + if(localStorage.q2adocs_github_plugins) { // Get saved list length for Plugins pluginsListUpdated = singleValue(retrievedPlugins[1])[2]; pluginsListUpdated = new Date(pluginsListUpdated).toISOString().split('T')[0]; pluginListLength = singleValue(retrievedPlugins[0])[2]; } - if(localStorage.q2adocs_gitHub_themes) { + if(localStorage.q2adocs_github_themes) { // Get saved list length for Themes themesListUpdated = singleValue(retrievedThemes[1])[2]; themesListUpdated = new Date(themesListUpdated).toISOString().split('T')[0]; From 65e8ccd676e59913defc1a26896e4973dce0a274 Mon Sep 17 00:00:00 2001 From: Chun <12265739+rxchun@users.noreply.github.com> Date: Sun, 15 Dec 2024 20:06:00 +0000 Subject: [PATCH 29/29] Fix for null child-repository --- js/GithubLinks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/GithubLinks.js b/js/GithubLinks.js index 81f9395..3a2700d 100644 --- a/js/GithubLinks.js +++ b/js/GithubLinks.js @@ -237,8 +237,8 @@ if(gitLinks != null && gitLinks.length) { // console.log(`${id} === ${link} === ${date}`); - if(gitLinks[index].parentElement.innerHTML.includes('➔')){ - gitLinks[index].closest('li').classList.add('child-repository'); + if(id != null && gitLinks[id].parentElement.innerHTML.includes('➔')){ + gitLinks[id].closest('li').classList.add('child-repository'); } if(id != null && link != null && date != null && max_q2a != null) {