diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 45692397be..e51a8b4302 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## ⚡️ 1.6.6 - Improved Search & Shortcuts [PR #175](https://github.com/Lissy93/dashy/pull/175) +- Refactors the search algorithm to improve performance and code reusability +- Updates search to ignore case, special characters and minor-typos +- Adds the option for user to specify tags, which can be used for searching + ## ✨ 1.6.5 - Adds support for Secure Authentication using Keycloak [PR #174](https://github.com/Lissy93/dashy/pull/174) - Major restructure of auth config - Implements keycloak support, adds docs and updates schema @@ -23,6 +28,7 @@ ## 💄 1.6.1 - Adds new Theme [PR #166](https://github.com/Lissy93/dashy/issues/166) - Adds Dashy theme, for use in the dev dashboard + ## ✨ 1.5.9 - New Minimal/ Startpage View [PR #155](https://github.com/Lissy93/dashy/issues/155) - Adds a new view, called minimal view, designed to be like a light-weight startpage - Implemented all the required features (filtering, opening methods, icons, etc) into minimal view diff --git a/.github/ISSUE_TEMPLATE/add-your-dashboard-to-the-showcase----.md b/.github/ISSUE_TEMPLATE/add-your-dashboard-to-the-showcase----.md deleted file mode 100644 index d6cf344cbc..0000000000 --- a/.github/ISSUE_TEMPLATE/add-your-dashboard-to-the-showcase----.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: "Add your Dashboard to the Showcase \U0001F5BC️" -about: Share a screenshot of your dashboard to the Readme showcase! -title: "[SHOWCASE_REQUEST]" -labels: '' -assignees: '' - ---- - -Please read the instructions here first: -https://github.com/Lissy93/dashy/blob/master/docs/showcase.md#submitting-your-dashboard - -### Complete the Following -- **Title of Dashboard**: -- **Link to Screenshot**: -- **Would you like your name/ username included**: Yes/ No -- **Link to your Website/ Profile/ Twitter** (optional) -- **Description** (optional) - -Either attach your screenshot here, or include a link to the CDN / image hosting service. diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 48bf362785..38486ec01c 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,7 +1,9 @@ name: Bug Report 🐛 description: Report something that's not working the way it's (probably) intended to title: '[BUG] ' -labels: '\U0001F41B Bug' +labels: ['🐛 Bug'] +assignees: + - lissy93 body: - type: dropdown id: environment @@ -34,6 +36,7 @@ body: attributes: label: Additional info description: Logs? Screenshots? Yes, please. + placeholder: If the issue happens during build-time, include terminal logs. For run-time errors, include browser logs which you can view in the Dev Tools (F12), under the Console tab. Take care to blank out any personal info. validations: required: false - type: checkboxes @@ -42,7 +45,7 @@ body: label: Please tick the boxes description: Before submitting, please ensure that options: - - label: You are using the latest, or recent version of Dashy + - label: You are using a [supported](https://github.com/Lissy93/dashy/blob/master/.github/SECURITY.md#supported-versions) version of Dashy (check the first two digits of the version number) required: true - label: You've checked that this [issue hasn't already been raised](https://github.com/Lissy93/dashy/issues?q=is%3Aissue) required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md deleted file mode 100644 index 97e0f091d3..0000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: "Feature Request \U0001F984" -about: Suggest an idea for future development of Dashy -title: "[FEATURE_REQUEST]" -labels: "\U0001F984 Feature Request" -assignees: Lissy93 - ---- - -**Is your feature request related to a problem? If so, please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Priority**: -Low: Nice to have / Medium: Would be useful / High: The app does not function without it diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000000..f01480f233 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,62 @@ +name: Bug Report 🐛 +description: Suggest an idea for future development of Dashy +title: '[FEATURE_REQUEST] <title>' +labels: ['\U0001F984 Feature Request'] + +body: + + # Field 1 - Is it bug-related + - type: textarea + id: issue + attributes: + label: Is your feature request related to a problem? If so, please describe. + description: + placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: false + + # Field 2 - Describe feature + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + placeholder: An outline of how you would like this to be implemented, include as much details as possible + validations: + required: true + + # Field 3 - Priority + - type: dropdown + id: priority + attributes: + label: Priority + description: How urgent is the development of this feature + options: + - Low (Nice-to-have) + - Medium (Would be very useful) + - High (The app does not function without it) + validations: + required: true + + # Field 3 - Can the user implement + - type: dropdown + id: canImplement + attributes: + label: Is this something you would be keen to implement + description: Are you raising this ticket in order to get an issue number for your PR? + options: + - 'No' + - 'Maybe' + - 'Yes!' + validations: + required: false + + # Final text + - type: markdown + attributes: + value: |- + ## Thanks 🙏 + Thank you for your feature suggestion, you should expect a reply within 48 hours :) + Please note that there is no guarantee that your idea will be implemented + If you haven't already done so, please Star the Dashy's repository on GitHub, to help other users discover it + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/showcase-addition.yml b/.github/ISSUE_TEMPLATE/showcase-addition.yml new file mode 100644 index 0000000000..5d3b66f1be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/showcase-addition.yml @@ -0,0 +1,58 @@ +name: Add your Dashboard to the Showcase 🌟 +description: Share a screenshot of your dashboard to the Readme showcase! +title: '[SHOWCASE] <title>' +labels: ['💯 Showcase'] + +body: +# 1 - Title +- type: input + id: title + attributes: + label: Title + description: Pick a title for your addition + placeholder: My Awesome Dashboard + validations: + required: false +# 2 - Link to Screenshot +- type: textarea + id: screenshot + attributes: + label: Screenshot + description: Either upload your screenshot here, or include a link to a png/jpg on a CDN / image hosting service + validations: + required: true +# 3 - Credit user +- type: dropdown + id: attribution + attributes: + label: Would you like your name/ username included? + description: This will be displayed above the screenshot to your dashboard in the showcase page + options: + - Yes + - No + validations: + required: true +# 4 - Social links +- type: input + id: links + attributes: + label: Link to your Website/ Profile/ Twitter (optional) + description: You can optionally have your name link to your profile or website. If you'd like this, include the URL to your site below + validations: + required: false +# 5 - Description +- type: textarea + id: description + attributes: + label: Description (Optional) + description: You can optionally also include a short description. If there's anything else you'd like to include, then put it here + validations: + required: false +# 6 - All done +- type: markdown + attributes: + value: |- + ## That's It! + Thanks for sharing your dashboard :) You will receive an update to this ticket once it's added to the showcase + validations: + required: false \ No newline at end of file diff --git a/README.md b/README.md index 9323879913..c2291eacd6 100644 --- a/README.md +++ b/README.md @@ -56,22 +56,25 @@ ## Features 🌈 -- Instant search by name, domain and tags - just start typing -- Full customizable keyboard shortcuts for navigation, filtering and launching apps -- Multiple built-in color themes, with UI color editor and support for custom CSS -- Customizable layout, sizes, text, component visibility, behavior and colors etc -- Many options for icons, including Font-Awesome support, auto-fetching favicon, images and emojis -- Option to show service status for each of your apps / links, for basic availability and uptime monitoring -- Choose how to launch apps, either in your browser, a pop-up modal or workspace view -- Option for full-screen background image, custom nav-bar links, html footer, title, and more -- Optional encrypted cloud backup and restore feature available -- Optional authentication with multi-user support and configurable privileges for protecting your dashboard -- Small bundle size, fully responsive UI and PWA makes the app easy to use on any device -- Easy to setup with Docker, or on bare metal, or with 1-Click cloud deployment -- Multi-language support, with more languages being added regularly -- Easy single-file YAML-based configuration, or configure app directly through the UI -- Strong focus on privacy -- Plus lots more... +- 🔎 Instant search by name, domain and tags - just start typing + customizable keyboard shortcuts +- 🎨 Multiple built-in color themes, with UI color editor and support for custom CSS +- 🧸 Many options for icons, including Font-Awesome support, auto-fetching favicon, images and emojis +- 🚦 Service status feature for each of your apps / links, for basic availability and uptime monitoring +- 💂 Optional authentication with multi-user support, configurable privileges and SSO support +- ☁ Optional encrypted cloud backup and restore feature available +- 💼 A workspace view, for easily switching between multiple apps at once +- 🛩️ A minimal view, for use as a fast-loading browser startpage +- 🖱️ Choose how to launch apps, either new tab, same tab, a pop-up modal or in the workspace view +- 🌎 Multi-language support, with more languages being added regularly +- 📏 Customizable layout, sizes, text, component visibility, behavior and colors etc +- 🖼️ Option for full-screen background image, custom nav-bar links, html footer, title, and more +- 🚀 Easy to setup with Docker, or on bare metal, or with 1-Click cloud deployment +- 🤏 Small bundle size, fully responsive UI and PWA makes the app easy to use on any device +- ⚙️ Easy single-file YAML-based configuration, with option to configure app directly through the UI +- ✨ Under active development with improvements and new features added regularly +- 🆓 100% free and open source +- 🔐 Strong focus on privacy +- 🌈 Plus lots more... ## Demo ⚡ @@ -328,6 +331,17 @@ Quickly finding and launching applications is the primary aim of Dashy. To that To start filtering, just start typing. No need to select the search bar or use any special key. You can then use either the tab key or arrow keys to select and move between results, and hit enter to launch the currently selected application. You can also use `Alt + Enter` on a selected app to launch it in a popup modal, `Ctrl + Enter` to open in new tab, or right-click on it to see all opening methods. +You can also add custom tags to a given item, to make finding them based on keywords easier. For example, in the following example, searching for 'Movies' will show 'Plex' + +```yaml + items: + - title: Plex + description: Media library + icon: favicon + url: https://plex.lab.local + tags: [ movies, videos, music ] +``` + For apps that you use regularly, you can set a custom keybinding. Use the `hotkey` parameter on a certain item to specify a numeric key, between `0 - 9`. You can then launch that app, by just pressing that key, which is very useful for services you use frequently. Example: @@ -567,17 +581,18 @@ If you're new to web development, I've put together a short [list of resources]( - 🛡️ [Authentication](/docs/authentication.md) - Guide to setting up authentication to protect your dashboard - 🧿 [Alternate Views](/docs/alternate-views.md) - Outline of available pages / views and item opening methods - 💾 [Backup & Restore](/docs/backup-restore.md) - Guide to Dashy's cloud sync feature -- 🚦 [Status Indicators](/docs/status-indicators.md) - Using Dashy to monitor uptime and status of your apps - 🧸 [Icons](/docs/icons.md) - Outline of all available icon types for sections and items - 🌐 [Language Switching](/docs/multi-language-support.md) - How to change language, add a language, or update text +- 🚦 [Status Indicators](/docs/status-indicators.md) - Using Dashy to monitor uptime and status of your apps +- 🔍 [Searching & Shortcuts](/docs/searching.md) - Finding and launching your apps, and using keyboard shortcuts - 🎨 [Theming](/docs/theming.md) - Complete guide to applying, writing and modifying themes and styles #### Misc -- [🔐 Privacy & Security](/docs/privacy.md) - List of requests, potential issues, and security resources -- [📄 License](/LICENSE) - Copy of the MIT License -- [⚖️ Legal](/.github/LEGAL.md) - Licenses of direct dependencies -- [📏 Code of Conduct](/.github/CODE_OF_CONDUCT.md) - Contributor Covenant Code of Conduct -- [🌳 Changelog](/.github/CHANGELOG.md) - Details of recent changes, and historical versions +- 🔐 [Privacy & Security](/docs/privacy.md) - List of requests, potential issues, and security resources +- 📄 [License](/LICENSE) - Copy of the MIT License +- ⚖️ [Legal](/.github/LEGAL.md) - Licenses of direct dependencies +- 📏 [Code of Conduct](/.github/CODE_OF_CONDUCT.md) - Contributor Covenant Code of Conduct +- 🌳 [Changelog](/.github/CHANGELOG.md) - Details of recent changes, and historical versions **[⬆️ Back to Top](#dashy)** diff --git a/docs/configuring.md b/docs/configuring.md index 2f4da7b18e..ca7add0e50 100644 --- a/docs/configuring.md +++ b/docs/configuring.md @@ -150,11 +150,13 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)** **`icon`** | `string` | _Optional_ | The icon for a given item. Can be a font-awesome icon, favicon, remote URL or local URL. See [`item.icon`](#sectionicon-and-sectionitemicon) **`target`** | `string` | _Optional_ | The opening method for when the item is clicked, either `newtab`, `sametab`, `modal` or `workspace`. Where `newtab` will open the link in a new tab, `sametab` will open it in the current tab, and `modal` will open a pop-up modal with the content displayed within that iframe. Note that for the iframe to load, you must have set the CORS headers to either allow `*` ot allow the domain that you are hosting Dashy on, for some websites and self-hosted services, this is already set. **`hotkey`** | `number` | _Optional_ | Give frequently opened applications a numeric hotkey, between `0 - 9`. You can then just press that key to launch that application. +**`tags`** | `string[]` | _Optional_ | A list of tags, which can be used for improved search **`statusCheck`** | `boolean` | _Optional_ | When set to `true`, Dashy will ping the URL associated with the current service, and display its status as a dot next to the item. The value here will override `appConfig.statusCheck` so you can turn off or on checks for a given service. Defaults to `appConfig.statusCheck`, falls back to `false` **`statusCheckUrl`** | `string` | _Optional_ | If you've enabled `statusCheck`, and want to use a different URL to what is defined under the item, then specify it here **`statusCheckHeaders`** | `object` | _Optional_ | If you're endpoint requires any specific headers for the status checking, then define them here **`color`** | `string` | _Optional_ | An optional color for the text and font-awesome icon to be displayed in. Note that this will override the current theme and so may not display well **`backgroundColor`** | `string` | _Optional_ | An optional background fill color for the that given item. Again, this will override the current theme and so might not display well against the background +**`provider`** | `string` | _Optional_ | The name of the provider for a given service, useful for when including hosted apps. In some themes, this is visible under the item name **[⬆️ Back to Top](#configuring)** diff --git a/docs/readme.md b/docs/readme.md index 9452120418..922d30a6ab 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -21,6 +21,7 @@ - [Icons](/docs/icons.md) - Outline of all available icon types for sections and items - [Language Switching](/docs/multi-language-support.md) - Details on how to switch language, or add a new locale - [Status Indicators](/docs/status-indicators.md) - Using Dashy to monitor uptime and status of your apps +- [Searching & Shortcuts](/docs/searching.md) - Finding and launching your apps, and using keyboard shortcuts - [Theming](/docs/theming.md) - Complete guide to applying, writing and modifying themes and styles ### Misc diff --git a/docs/searching.md b/docs/searching.md new file mode 100644 index 0000000000..b8540e86f1 --- /dev/null +++ b/docs/searching.md @@ -0,0 +1,53 @@ +# Keyboard Shortcuts + +## Searching +One of the primary purposes of Dashy is to allow you to quickly find and launch a given app. To make this as quick as possible, there is no need to touch the mouse, or press a certain key to begin searching - just start typing. Results will be filtered in real-time. No need to worry about case, special characters or small typos, these are taken care of, and your results should appear. + +## Navigating +You can navigate through your items or search results using the keyboard. You can use <kbd>Tab</kbd> to cycle through results, and <kbd>Shift</kbd> + <kbd>Tab</kbd> to go backwards. Or use the arrow keys, <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd> and <kbd>←</kbd>. + +## Launching Apps +You can launch a elected app by hitting <kbd>Enter</kbd>. This will open the app using your default opening method, specified in `target` (either `newtab`, `sametab`, `modal` or `workspace`). You can also use <kbd>Alt</kbd> + <kbd>Enter</kbd> to open the app in a pop-up modal, or <kbd>Ctrl</kbd> + <kbd>Enter</kbd> to open it in a new tab. For all available opening methods, just right-click on an item, to bring up the context menu. + +## Tags +By default, items are filtered by the `title` attribute, as well as the hostname (extracted from `url`), the `provider` and `description`. If you need to find results based on text which isn't included in these attributes, then you can add `tags` to a given item. + +```yaml + items: + - title: Plex + description: Media library + icon: favicon + url: https://plex.lab.local + tags: [ movies, videos, music ] + - title: FreshRSS + description: RSS Reader + icon: favicon + url: https://freshrss.lab.local + tags: [ news, updates, blogs ] + +``` + +In the above example, Plex will be visible when searching for 'movies', and FreshRSS with 'news' + + +## Custom Hotkeys +For apps that you use regularly, you can set a custom keybinding. Use the `hotkey` parameter on a certain item to specify a numeric key, between `0 - 9`. You can then launch that app, by just pressing that key, which is much quicker than searching for it, if it's an app you use frequently. + +```yaml +- title: Bookstack + icon: far fa-books + url: https://bookstack.lab.local/ + hotkey: 2 +- title: Git Tea + icon: fab fa-git + url: https://git.lab.local/ + target: workspace + hotkey: 3 +``` + +In the above example, pressing <kbd>2</kbd> will launch Bookstack. Or hitting <kbd>3</kbd> will open Git in the workspace view. + + +## Clearing Search +You can clear your search term at any time, by pressing <kbd>Esc</kbd>. +This can also be used to close an open pop-up modal. diff --git a/docs/showcase.md b/docs/showcase.md index b03072263a..4c899078e1 100644 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -30,6 +30,12 @@ --- +### Dashy Live +> By [@Lissy93](https://github.com/lissy93) +> A dashboard I made to manage all project development links from one place + +![screenshot-dashy-live](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/10-dashy-live.png) + ### CFT Toolbox ![screenshot-cft-toolbox](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/3-cft-toolbox.png) diff --git a/docs/showcase/10-dashy-live.png b/docs/showcase/10-dashy-live.png new file mode 100644 index 0000000000..b462ce0745 Binary files /dev/null and b/docs/showcase/10-dashy-live.png differ diff --git a/package.json b/package.json index 1088671622..522bf7cfd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Dashy", - "version": "1.6.5", + "version": "1.6.6", "license": "MIT", "main": "server", "scripts": { diff --git a/src/components/Configuration/CustomCss.vue b/src/components/Configuration/CustomCss.vue index 35ce907038..8ffa02c9f5 100644 --- a/src/components/Configuration/CustomCss.vue +++ b/src/components/Configuration/CustomCss.vue @@ -1,11 +1,17 @@ <template> <div class="css-editor-outer"> - <textarea class="css-editor" v-model="customCss" /> - <button class="save-button" @click="save()">{{ $t('config.css-save-btn') }}</button> - <p class="quick-note"> - <b>{{ $t('config.css-note-label') }}:</b> - {{ $t('config.css-note-l1') }} {{ $t('config.css-note-l2') }} {{ $t('config.css-note-l3') }} - </p> + <!-- Add raw custom CSS --> + <div class="css-wrapper"> + <h2 class="css-input-title">Custom CSS</h2> + <textarea class="css-editor" v-model="customCss" /> + <button class="save-button" @click="save()">{{ $t('config.css-save-btn') }}</button> + <p class="quick-note"> + <b>{{ $t('config.css-note-label') }}:</b> + {{ $t('config.css-note-l1') }} {{ $t('config.css-note-l2') }} {{ $t('config.css-note-l3') }} + </p> + </div> + + <!-- UI color configurator --> <CustomThemeMaker :themeToEdit="currentTheme" class="color-config" /> </div> </template> @@ -31,9 +37,11 @@ export default { }; }, methods: { + /* A regex to validate the users CSS */ validate(css) { - return css === '' || css.match(/((?:^\s*)([\w#.@*,:\-.:>,*\s]+)\s*{(?:[\s]*)((?:[A-Za-z\- \s]+[:]\s*['"0-9\w .,/()\-!%]+;?)*)*\s*}(?:\s*))/gmi); + return css === '' || css.match(/([#.@]?[\w.:> ]+)[\s]{[\r\n]?([A-Za-z\- \r\n\t]+[:][\s]*[\w ./()\-!]+;[\r\n]*(?:[A-Za-z\- \r\n\t]+[:][\s]*[\w ./()\-!]+;[\r\n]*(2)*)*)}/gmi); }, + /* Save custom CSS in browser, call inject, and show success message */ save() { let msg = ''; if (this.validate(this.customCss)) { @@ -48,6 +56,7 @@ export default { } this.$toasted.show(msg); }, + /* Formats CSS, and applies it to page */ inject(userStyles) { const cleanedCss = userStyles.replace(/<\/?[^>]+(>|$)/g, ''); const style = document.createElement('style'); @@ -63,6 +72,12 @@ export default { div.css-editor-outer { text-align: center; padding-bottom: 1rem; + display: flex; + flex-direction: column; + + h2.css-input-title { + margin: 0.5rem 0 0.2rem; + } } button.save-button { @@ -104,6 +119,8 @@ p.quick-note { width: 80%; margin: 1rem auto; padding: 0.5rem; + font-size: 0.9rem; + opacity: var(--dimming-factor); border-radius: var(--curve-factor); } diff --git a/src/components/LinkItems/Item.vue b/src/components/LinkItems/Item.vue index f4a433c646..1a33a8181c 100644 --- a/src/components/LinkItems/Item.vue +++ b/src/components/LinkItems/Item.vue @@ -12,7 +12,7 @@ :style="`--open-icon: ${getUnicodeOpeningIcon()}; ${customStyles}`" > <!-- Item Text --> - <div :class="`tile-title ${!icon? 'bounce': ''}`" :id="`tile-${id}`" > + <div :class="`tile-title ${!icon? 'bounce no-icon': ''}`" :id="`tile-${id}`" > <span class="text">{{ title }}</span> <p class="description">{{ description }}</p> </div> @@ -319,6 +319,12 @@ export default { .tile-title { min-width: 100px; max-width: 160px; + &.no-icon { + text-align: left; + width: 100%; + max-width: inherit; + margin-left: 0.5rem; + } } } /* Large Tile Specific Themes */ diff --git a/src/styles/color-themes.scss b/src/styles/color-themes.scss index 48e63002df..9171b192f4 100644 --- a/src/styles/color-themes.scss +++ b/src/styles/color-themes.scss @@ -926,6 +926,8 @@ html[data-theme="dashy-docs"] { border: none; color: var(--background); font-weight: bold; + min-width: 5rem; + text-align: center; } .minimal-section-heading { diff --git a/src/utils/ConfigSchema.json b/src/utils/ConfigSchema.json index ff152e1d1b..a21798d99f 100644 --- a/src/utils/ConfigSchema.json +++ b/src/utils/ConfigSchema.json @@ -471,6 +471,14 @@ "type": "number", "description": "A numeric shortcut key, between 0 and 9. Useful for quickly launching frequently used applications" }, + "tags": { + "type": "array", + "description": "Tags, which can be used for improved search", + "maxItems": 12, + "items": { + "type": "string" + } + }, "color": { "type": "string", "description": "A custom fill color of the item" diff --git a/src/utils/Search.js b/src/utils/Search.js new file mode 100644 index 0000000000..e50b88bf2f --- /dev/null +++ b/src/utils/Search.js @@ -0,0 +1,52 @@ +/* Dashy: Licensed under MIT, (C) Alicia Sykes 2021 <https://aliciasykes.com> */ + +/* Tile filtering utility */ + +/** + * Extracts the site name from domain + * @param {string} url The URL to process + * @returns {string} The hostname from URL + */ +const getDomainFromUrl = (url) => { + if (!url) return ''; + const urlPattern = /^(?:https?:\/\/)?(?:w{3}\.)?([a-z\d.-]+)\.(?:[a-z.]{2,10})(?:[/\w.-]*)*/; + const domainPattern = url.match(urlPattern); + return domainPattern ? domainPattern[1] : ''; +}; + +/** + * Compares search term to a given data attribute + * Ignores case, special characters and order + * @param {string or other} compareStr The value to compare to + * @param {string} searchStr The users search term + * @returns {boolean} true if a match, otherwise false + */ +const filterHelper = (compareStr, searchStr) => { + if (!compareStr) return false; + const process = (input) => input.toString().toLowerCase().replace(/[^\w\s]/gi, ''); + return process(compareStr).includes(process(searchStr)); +}; + +/** + * Filter tiles based on users search term, and returns a filtered list + * Will match based on title, description, provider, hostname from url and tags + * Ignores case, special characters and other irrelevant things + * @param {array} allTiles An array of tiles + * @param {string} searchTerm The users search term + * @returns A filtered array of tiles + */ +const search = (allTiles, searchTerm) => { + if (!allTiles) return []; // If no data, then skip + return allTiles.filter((tile) => { + const { + title, description, provider, url, tags, + } = tile; + return filterHelper(title, searchTerm) + || filterHelper(provider, searchTerm) + || filterHelper(description, searchTerm) + || filterHelper(tags, searchTerm) + || filterHelper(getDomainFromUrl(url), searchTerm); + }); +}; + +export default search; diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 4d3d400e04..10d18ad1fb 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -38,29 +38,27 @@ module.exports = { builtInThemes: [ 'callisto', 'thebe', - 'dracula', + 'oblivion', 'material', 'material-dark', + 'dracula', 'colorful', - 'nord', - 'nord-frost', + 'dashy-docs', 'minimal-dark', 'minimal-light', - 'oblivion', + 'nord', + 'nord-frost', + 'cyberpunk', 'matrix', 'matrix-red', - 'hacker-girl', 'raspberry-jam', 'bee', 'tiger', - 'blue-purple', + 'vaporware', 'material-original', 'material-dark-original', - 'cyberpunk', - 'vaporware', 'high-contrast-dark', 'high-contrast-light', - 'dashy-docs', ], /* Which structural components should be visible by default */ visibleComponents: { diff --git a/src/views/Home.vue b/src/views/Home.vue index b13c818e7f..a157ddc95c 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -1,3 +1,4 @@ +<!-- Main homepage for default view --> <template> <div class="home" :style="getBackgroundImage()"> <!-- Search bar, layout options and settings --> @@ -9,7 +10,7 @@ :displayLayout="layout" :iconSize="itemSizeBound" :externalThemes="getExternalCSSLinks()" - :sections="getSections(sections)" + :sections="allSections" :appConfig="appConfig" :pageInfo="pageInfo" :modalOpen="modalOpen" @@ -19,17 +20,19 @@ <div v-if="checkTheresData(sections)" :class="`item-group-container orientation-${layout} item-size-${itemSizeBound}`"> <Section - v-for="(section, index) in getSections(sections)" + v-for="(section, index) in filteredTiles" :key="index" :title="section.name" :icon="section.icon || undefined" :displayData="getDisplayData(section)" :groupId="`section-${index}`" - :items="filterTiles(section.items)" + :items="filterTiles(section.items, searchValue)" + :searchTerm="searchValue" :itemSize="itemSizeBound" @itemClicked="finishedSearching()" @change-modal-visibility="updateModalVisibility" - :class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''" + :class=" + (searchValue && filterTiles(section.items, searchValue).length === 0) ? 'no-results' : ''" /> </div> <!-- Show message when there's no data to show --> @@ -43,6 +46,7 @@ import SettingsContainer from '@/components/Settings/SettingsContainer.vue'; import Section from '@/components/LinkItems/Section.vue'; +import SearchUtil from '@/utils/Search'; import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults'; export default { @@ -63,6 +67,21 @@ export default { modalOpen: false, // When true, keybindings are disabled }), computed: { + /* Combines sections from config file, with those in local storage */ + allSections() { + // If the user has stored sections in local storage, return those + const localSections = localStorage[localStorageKeys.CONF_SECTIONS]; + if (localSections) { + const json = JSON.parse(localSections); + if (json.length >= 1) return json; + } + // Otherwise, return the usuall data from conf.yml + return this.sections; + }, + filteredTiles() { + const sections = this.allSections; + return sections.filter((section) => this.filterTiles(section.items, this.searchValue)); + }, /* Updates layout (when button clicked), and saves in local storage */ layoutOrientation: { get() { return this.appConfig.layout || Defaults.layout; }, @@ -86,17 +105,6 @@ export default { const localSections = localStorage[localStorageKeys.CONF_SECTIONS]; return (sections && sections.length >= 1) || (localSections && localSections.length >= 1); }, - /* Returns sections from local storage if available, otherwise uses the conf.yml */ - getSections(sections) { - // If the user has stored sections in local storage, return those - const localSections = localStorage[localStorageKeys.CONF_SECTIONS]; - if (localSections) { - const json = JSON.parse(localSections); - if (json.length >= 1) return json; - } - // Otherwise, return the usuall data from conf.yml - return sections; - }, /* Updates local data with search value, triggered from filter comp */ searching(searchValue) { this.searchValue = searchValue || ''; @@ -105,26 +113,9 @@ export default { finishedSearching() { this.$refs.filterComp.clearFilterInput(); }, - /* Extracts the site name from domain, used for the searching functionality */ - getDomainFromUrl(url) { - if (!url) return ''; - const urlPattern = /^(?:https?:\/\/)?(?:w{3}\.)?([a-z\d.-]+)\.(?:[a-z.]{2,10})(?:[/\w.-]*)*/; - const domainPattern = url.match(urlPattern); - return domainPattern ? domainPattern[1] : ''; - }, /* Returns only the tiles that match the users search query */ - filterTiles(allTiles) { - if (!allTiles) return []; - return allTiles.filter((tile) => { - const { - title, description, provider, url, - } = tile; - const searchTerm = this.searchValue.toLowerCase(); - return (title && title.toLowerCase().includes(searchTerm)) - || (provider && provider.toLowerCase().includes(searchTerm)) - || (description && description.toLowerCase().includes(searchTerm)) - || this.getDomainFromUrl(url).includes(searchTerm); - }); + filterTiles(allTiles, searchTerm) { + return SearchUtil(allTiles, searchTerm); }, /* Returns optional section display preferences if available */ getDisplayData(section) { @@ -163,8 +154,8 @@ export default { /* Checks if any sections or items use icons from a given CDN */ checkIfIconLibraryNeeded(prefix) { let isNeeded = false; - if (!this.sections) return false; - this.sections.forEach((section) => { + if (!this.allSections) return false; + this.allSections.forEach((section) => { if (section.icon && section.icon.includes(prefix)) isNeeded = true; section.items.forEach((item) => { if (item.icon && item.icon.includes(prefix)) isNeeded = true; @@ -203,11 +194,11 @@ export default { }, /* Returns true if there is more than 1 sub-result visible during searching */ checkIfResults() { - if (!this.sections) return false; + if (!this.allSections) return false; else { let itemsFound = true; - this.sections.forEach((section) => { - if (this.filterTiles(section.items).length > 0) itemsFound = false; + this.allSections.forEach((section) => { + if (this.filterTiles(section.items, this.searchValue).length > 0) itemsFound = false; }); return itemsFound; } diff --git a/src/views/Minimal.vue b/src/views/Minimal.vue index c0602b04d1..f8b4750659 100644 --- a/src/views/Minimal.vue +++ b/src/views/Minimal.vue @@ -54,6 +54,7 @@ import MinimalSection from '@/components/MinimalView/MinimalSection.vue'; import MinimalHeading from '@/components/MinimalView/MinimalHeading.vue'; import MinimalSearch from '@/components/MinimalView/MinimalSearch.vue'; import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHelper'; +import SearchUtil from '@/utils/Search'; import Defaults, { localStorageKeys } from '@/utils/defaults'; import ConfigLauncher from '@/components/Settings/ConfigLauncher'; @@ -122,16 +123,7 @@ export default { /* Returns only the tiles that match the users search query */ filterTiles(allTiles) { if (!allTiles) return []; - return allTiles.filter((tile) => { - const { - title, description, provider, url, - } = tile; - const searchTerm = this.searchValue.toLowerCase(); - return (title && title.toLowerCase().includes(searchTerm)) - || (provider && provider.toLowerCase().includes(searchTerm)) - || (description && description.toLowerCase().includes(searchTerm)) - || this.getDomainFromUrl(url).includes(searchTerm); - }); + return SearchUtil(allTiles, this.searchValue); }, /* Update data when modal is open (so that key bindings can be disabled) */ updateModalVisibility(modalState) {