├── README.md ├── ghost-dropdown.css └── ghost-dropdown.js /README.md: -------------------------------------------------------------------------------- 1 | # Ghost Dynamic Dropdown Menu 2 | Ghost does not have the options to create a dropdown menu from the ghost admin dashboard. So we tried to make that feature for ghost users. 3 | 4 | ## Installation 5 | 6 | [Download Latest Release](https://github.com/themeix/ghost-dynamic-dropdown/releases/latest) 7 | 8 | **Include CSS** 9 | 10 | ```html 11 | 12 | ``` 13 | 14 | **Include script** 15 | 16 | ```html 17 | 18 | ``` 19 | 20 | At the bottom of the script there has option to change the value for child dropdown item selector, menu select, css class etc. Here following the options : 21 | 22 | ### Options 23 | 24 | | Name | Options Value | Details | 25 | | -------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------- | 26 | | targetElement | `nav.ul li` | DOM selector of Menu `ul` | 27 | | hasChildrenClasses | `menu-item-has-children` | add class for the parrent item. ( has CSS dependency ) | 28 | | hasChildrenIcon | ▼ (SVG icon) | Use any SVG icon or text as dropdown icon | 29 | | hasChildDetectText | `[has_child]` **used in admin dashboard** | Parrent menu item selector | 30 | | submenuUlClasses | `ghost-submenu` | CSS class for the submenu items `ul` ( has CSS dependency ) | 31 | | subitemDetectText | `[subitem]` **used in admin dashboard** | child menu item selector | 32 | | subitemLiClasses | subitem | CSS class for the submenu item | 33 | | multi_level | true | Support Multi Level Dropdown | 34 | | mega_menu | true | Support Mega Menu | 35 | 36 | #### SVG Icon Example 37 | ``` 38 | 39 | ``` 40 | 41 | #### Example Code 42 | 43 | ```js 44 | $(document).ready(function () { 45 | ghost_dropdown({ 46 | targetElement: "nav.ul li", 47 | hasChildrenClasses: "menu-item-has-children", 48 | hasChildrenIcon: "", 49 | hasChildDetectText: "[has_child]", 50 | submenuUlClasses: "ghost-submenu", 51 | subitemDetectText: "[subitem]", 52 | subitemLiClasses: "subitem", 53 | multi_level: false, 54 | mega_menu: false 55 | }); 56 | }); 57 | ``` 58 | 59 | 60 | 61 | ### Dynamic Menu Supported Theme ( Made by Themeix ) 62 | 63 | https://themeix.com/product/learn-premium-lms-theme-for-ghost-cms/ 64 | 65 | https://themeix.com/product/pidkast-ghost-cms-theme-for-podcast/ 66 | 67 | https://themeix.com/product/newsfeed-news-magazine-ghost-theme/ 68 | 69 | 70 | Here is the documentation link about dynamic dropdwon : 71 | 72 | https://support.themeix.com/how-to-add-dropdown-navigation-in-newsfeed/ 73 | 74 | -------------------------------------------------------------------------------- /ghost-dropdown.css: -------------------------------------------------------------------------------- 1 | li.menu-item-has-children { 2 | position: relative; 3 | padding-right: 20px!important; 4 | display: inline-block; 5 | } 6 | .menu-item-has-children svg { 7 | position: absolute; 8 | right: -6px; 9 | top: 56%; 10 | transform: translate(-0%, -50%) scale(1); 11 | } 12 | 13 | ul.ghost-submenu { 14 | background: #fff; 15 | color: #000; 16 | padding: 10px 20px; 17 | border-radius: 5px; 18 | width: 200px; 19 | max-width: 200px; 20 | position: absolute; 21 | visibility: hidden; 22 | z-index: 1; 23 | opacity: 0; 24 | top: 30px; 25 | transition: 0.3s; 26 | box-shadow: 0 1px 5px 0 rgb(0 0 0 / 14%); 27 | left: 0; 28 | } 29 | 30 | li.menu-item-has-children:hover ul.ghost-submenu { 31 | visibility: visible!important; 32 | opacity: 1!important; 33 | top: 45px!important; 34 | } 35 | 36 | ul.ghost-submenu li { 37 | list-style: none; 38 | } 39 | 40 | ul li { 41 | opacity: 0; 42 | } 43 | 44 | li.nav-item.menu-item-has-children.menu-item-has-megamenu ul.ghost-submenu { 45 | width: 100vh; 46 | } 47 | -------------------------------------------------------------------------------- /ghost-dropdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ghost-dynamic-dropdown 1.1.0 (https://github.com/themeix/ghost-dynamic-dropdown) 3 | * A simple script for dynamic dorpdown & mega menu for Ghost Blogging Platform. 4 | * Copyright 2022 Themeix (https://themeix.com) 5 | * Released under MIT License 6 | * Released on: Jul 25, 2021 7 | */ 8 | 9 | 10 | 11 | (function ($) { 12 | "use strict"; 13 | 14 | function multiLevel(targetElement = "ul li", mLhasSubmenu = "mL-has-submenu", mLsubmenu = "mL-submenu") { 15 | let mLparentDetecttext = "[-]"; 16 | let mLchildDetectText = "[--]"; 17 | let mLdomArrayElement = []; 18 | let mLparentIndex = []; 19 | let mLparentLen = 0; 20 | 21 | // Find Dropdown parent element 22 | $(`${targetElement} li`).each(function (index, element) { 23 | if ($(this).text().includes(mLparentDetecttext)) { 24 | mLparentIndex.push(index); // Make dropdown parent array index 25 | mLparentLen++; 26 | 27 | $(this).push(element); 28 | if (!$(this).hasClass('menu-item-has-children')) { 29 | $(this).addClass(mLhasSubmenu); // Add claas in dropdown element 30 | } 31 | $(this).append(``); // Append submenu element 32 | } 33 | }); 34 | 35 | 36 | 37 | let elIndex; 38 | // Code last multilevel 39 | let lastMlElementText = $(`.${mLhasSubmenu}`).last().text(); 40 | // console.log(lastMlElement); 41 | 42 | // Using loop to reach dropdown parent element 43 | for (let i = 0; i < mLparentLen; i++) { 44 | 45 | elIndex = 0 // Initial elemet value 46 | 47 | // Find subitem element 48 | $(`${targetElement} li`).each(function (index, element) { 49 | let mLsubitem = $(this).text().includes(mLchildDetectText); // Find subitem element 50 | 51 | 52 | if (mLsubitem) { 53 | 54 | if (elIndex + 1 >= mLparentIndex[i + 1] + 1) { // Each loop will be break 55 | return false; //Stoped each loop 56 | } 57 | 58 | if (elIndex <= mLparentIndex[i + 1] || elIndex >= mLparentIndex[mLparentIndex.length - 1]) { 59 | 60 | if (!mLparentIndex.includes(index)) { //Check if not index already insert 61 | mLdomArrayElement.push(element); // Incert subitem element in dom array 62 | mLparentIndex.push(index); // incert subitem index in indexPush array 63 | } 64 | } 65 | 66 | } 67 | elIndex++; // increase element index value 68 | }); 69 | 70 | 71 | $(`.${mLhasSubmenu} ul.${mLsubmenu}:eq(${i})`).append(mLdomArrayElement); // Append related subitem dom element into submenu 72 | 73 | mLdomArrayElement = []; // Make dom array element empty. 74 | } 75 | 76 | let lastMlElementIndex = 0; // Find subitem element 77 | let lastChildIndex = 0, lastChildElementText; 78 | 79 | 80 | $(`${targetElement} li`).each(function (index, element) { 81 | let lastMlElement = $(this).text().includes(lastMlElementText); // Find subitem element 82 | 83 | if (lastMlElement) { 84 | if (!$(this).hasClass('mLlastPrentElement')) { 85 | $(this).addClass('mLlastPrentElement'); 86 | lastChildElementText = $(this).parent().children('li').last().text(); 87 | lastMlElementIndex = index; 88 | } 89 | 90 | } 91 | 92 | if ($(this).text().includes(lastChildElementText)) { 93 | lastChildIndex = index; 94 | } 95 | 96 | if (lastMlElementIndex < index && lastMlElementIndex > 0) { 97 | $(this).addClass('mLlastChildElements'); 98 | $(".mLlastPrentElement ul").append($(`.mLlastChildElements`)); 99 | if (lastChildIndex == index) { 100 | return false; 101 | } 102 | } 103 | }); 104 | 105 | remove_text(mLhasSubmenu, mLparentDetecttext); 106 | remove_text('subitem', mLchildDetectText); 107 | 108 | } 109 | 110 | function remove_text(textClass, replacedText) { 111 | 112 | const mLhasSubmenuEL = $(`.${textClass}`); 113 | mLhasSubmenuEL.each(function () { 114 | if ($(this).find("> a:first").text().includes(replacedText)) { 115 | let textFull = $(this).find("> a:first").text(); // Find has child inner text 116 | $(this).find("> a:first").text(textFull.replaceAll(replacedText, "")); 117 | } 118 | }); 119 | } 120 | 121 | 122 | function megamenu(hasMegaMenuClasses = "menu-item-has-megamenu", col = 3, item_slice = 4, hasMegaMenuDetectText = "[has_megamenu]", submenuUlClasses = "ghost-submenu") { 123 | let megaMenuEl = $(`.${hasMegaMenuClasses} li`); 124 | $(`.${hasMegaMenuClasses} .${submenuUlClasses}`).addClass('row'); 125 | let titleText = []; 126 | let titleIndex = 0; 127 | megaMenuEl.each(function (index, element) { 128 | if ($(this).text().includes("[title]")) { 129 | titleIndex++; 130 | $(this).addClass("megamenu-title"); 131 | titleText.push($(this).text()) 132 | $(".megamenu-title").empty(); 133 | } 134 | }); 135 | 136 | for (let i = 0; i < megaMenuEl.length; i += item_slice) { 137 | megaMenuEl.slice(i, i + item_slice).wrapAll(`
`); 138 | // console.log(titleText[i]); 139 | // console.log(titleText); 140 | } 141 | 142 | for (let i = 0; i < titleText.length; i++) { 143 | $(`.${submenuUlClasses} > div:eq(${i})`).prepend(`
${titleText[i]}
`); 144 | $(".megamenu-title-text").text(titleText[i].replaceAll("[title]", "")); 145 | } 146 | remove_text(hasMegaMenuClasses, hasMegaMenuDetectText); 147 | } 148 | 149 | function ghost_dropdown(options) { 150 | 151 | // Default options 152 | let defultOptions = { 153 | targetElement: "nav.ul li", 154 | hasChildrenClasses: "menu-item-has-children", 155 | hasChildDetectText: "[has_child]", 156 | hasChildrenIcon: "", 157 | hasMegaMenuDetectText: "[has_megamenu]", 158 | hasMegaMenuClasses: "menu-item-has-megamenu", 159 | submenuUlClasses: "ghost-submenu", 160 | subitemDetectText: "[subitem]", 161 | subitemLiClasses: "subitem" 162 | } 163 | 164 | //Marge defaultOptions 165 | options = { 166 | ...defultOptions, 167 | ...options 168 | } 169 | 170 | 171 | // Target Element 172 | let targetElement = options.targetElement; 173 | 174 | //Default value 175 | let hasChildrenClasses = options.hasChildrenClasses; 176 | let hasChildDetectText = options.hasChildDetectText; 177 | let hasMegaMenuClasses = options.hasMegaMenuClasses; 178 | let hasMegaMenuDetectText = options.hasMegaMenuDetectText; 179 | let hasChildrenIcon = options.hasChildrenIcon; 180 | let submenuUlClasses = options.submenuUlClasses; 181 | let subitemDetectText = options.subitemDetectText; 182 | let subitemLiClasses = options.subitemLiClasses; 183 | 184 | 185 | // Declare neccesary variable 186 | let parentEl = $(targetElement); 187 | let childEL = $(targetElement); 188 | let parentLen = 0; 189 | let domArrayElement = []; 190 | let indexPush = []; 191 | let elIndex = 0; 192 | let parentIndex = []; 193 | 194 | $(`${targetElement}`).parent().addClass('ghost-dropdown-menu'); 195 | 196 | let that; 197 | // Find Dropdown parent element 198 | parentEl.each(function (index, element) { 199 | if ($(this).text().indexOf(hasChildDetectText) >= 0) { 200 | parentIndex.push(index); // Make dropdown parent array index 201 | parentLen++; 202 | 203 | $(this).push(element); 204 | $(this).addClass(hasChildrenClasses); // Add claas in dropdown element 205 | 206 | $(this).append(``); // Append submenu element 207 | 208 | $(targetElement).css("opacity", "1"); 209 | } 210 | 211 | if ($(this).text().includes(hasMegaMenuDetectText)) { 212 | $(this).addClass(hasMegaMenuClasses); 213 | that = $(this); 214 | } 215 | }); 216 | 217 | $(targetElement).css("opacity", "1"); 218 | 219 | $(`.${hasChildrenClasses}`).append(hasChildrenIcon); 220 | 221 | if(!$(hasChildrenClasses).length){ 222 | $(targetElement).css("opacity", "1"); 223 | } 224 | 225 | // Using loop to reach dropdown parent element 226 | for (let i = 0; i < parentLen; i++) { 227 | 228 | elIndex = 0 // Initial elemet value 229 | 230 | // Find subitem element 231 | childEL.each(function (index, element) { 232 | let subitem = $(this).text().includes(subitemDetectText); // Find subitem element 233 | 234 | if (subitem) { 235 | 236 | if (elIndex >= parentIndex[i + 1]) { // Each loop will be break 237 | return false; //Stoped each loop 238 | } 239 | 240 | if (elIndex <= parentIndex[i + 1] || elIndex >= parentIndex[parentIndex.length - 1]) { 241 | 242 | if (!indexPush.includes(index)) { //Check if not index already insert 243 | $(this).addClass(subitemLiClasses); // Add class in subitem element 244 | let st = $(this).children().text(); // Find subitem inner text 245 | $(this).children().text(st.replaceAll(subitemDetectText, "")); // Replace subitem inner text 246 | 247 | domArrayElement.push(element); // Incert subitem element in dom array 248 | indexPush.push(index); // incert subitem index in indexPush array 249 | 250 | } 251 | } 252 | 253 | } 254 | elIndex++; // increase element index value 255 | }); 256 | 257 | 258 | $(`.${hasChildrenClasses} ul.${submenuUlClasses}:eq(${i})`).append(domArrayElement); // Append related subitem dom element into submenu 259 | 260 | domArrayElement = []; // Make dom array element empty. 261 | 262 | } 263 | remove_text(hasChildrenClasses, hasChildDetectText); 264 | 265 | 266 | if (options.multi_level) { 267 | multiLevel(); 268 | } 269 | if (options.mega_menu) { 270 | megamenu(hasMegaMenuClasses, 3, 4, hasMegaMenuDetectText, submenuUlClasses); 271 | } 272 | 273 | 274 | } 275 | 276 | $(document).ready(function () { 277 | ghost_dropdown({ 278 | targetElement: "nav.ul li", 279 | hasChildrenClasses: "menu-item-has-children", 280 | hasChildrenIcon: "", 281 | hasChildDetectText: "[has_child]", 282 | submenuUlClasses: "ghost-submenu", 283 | subitemDetectText: "[subitem]", 284 | subitemLiClasses: "subitem", 285 | multi_level: true, 286 | mega_menu: true 287 | }); 288 | }); 289 | 290 | 291 | }(jQuery)); 292 | --------------------------------------------------------------------------------