🔀 Merge pull request #292 from Lissy93/FEATURE/advanced-item-targets

[FEATURE] Improved Item Opening Method Options
Fixes #289
This commit is contained in:
Alicia Sykes 2021-10-23 01:44:59 +01:00 committed by GitHub
commit a42b9ee7c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 99 additions and 35 deletions

View File

@ -1,5 +1,11 @@
# Changelog
## ✨ 1.8.8 - Improved Item Targets [PR #292](https://github.com/Lissy93/dashy/pull/292)
- Adds support for `_top` and `_parent` anchor targets on items, Re: #289
- Adds `appConfig.defaultOpeningMethod` option to specify default target
- Adds new icons to show items opening method on hover
- Refactors target checking, updates item target docs and schema
## ⚡️ 1.8.7 - Bug Fixes and Improvements [PR #273](https://github.com/Lissy93/dashy/pull/273)
- Clean URLs without the hash, now using history-mode routing
- New initial main example conf.yml

View File

@ -304,6 +304,9 @@ One of the primary purposes of Dashy is to make launching commonly used apps and
- `newtab` - The app will be launched in a new tab
- `modal` - Launch app in a resizable/ movable popup modal on the current page
- `workspace` - Changes to Workspace view, and launches app
- `top` - Opens in the top-most browsing context, useful if your accessing Dashy through an iframe
You can also set the default opening method, which will be applied to all items that don't have a specified target, using `appConfig.defaultOpeningMethod`, to one of the above values.
Even if the target is not set (or is set to `sametab`), you can still launch any given app in an alternative method: Alt + Click will open the modal, and Ctrl + Click will open in a new tab. You can also right-click on any item to see all options (as seen in the screenshot below). This custom context menu can be disabled by setting `appConfig.disableContextMenu: true`.

View File

@ -37,9 +37,12 @@ Dashy supports several different ways to launch your apps. The default opening m
- `sametab` - The app will be launched in the current tab
- `newtab` - The app will be launched in a new tab
- `top` - Opens in the top-most browsing context, useful if your accessing Dashy through an iframe
- `modal` - Launch app in a resizable/ movable popup modal on the current page
- `workspace` - Changes to Workspace view, and launches app
You can also set the default opening method, which will be applied to all items that don't have a specified target, using `appConfig.defaultOpeningMethod`, to one of the above values.
Even if the target is not set (or is set to `sametab`), you can still launch any given app in an alternative method: Alt + Click will open the modal, and Ctrl + Click will open in a new tab. You can also right-click on any item to see all options (as seen in the screenshot below). This custom context menu can be disabled by setting `appConfig.disableContextMenu: true`.
<p align="center">

View File

@ -74,6 +74,7 @@ Tips:
--- | --- | --- | ---
**`language`** | `string` | _Optional_ | The 2 (or 4-digit) [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language, e.g. `en` or `en-GB`. This must be a language that the app has already been [translated](https://github.com/Lissy93/dashy/tree/master/src/assets/locales) into. If your language is unavailable, Dashy will fallback to English. By default Dashy will attempt to auto-detect your language, although this may not work on some privacy browsers.
**`startingView`** | `enum` | _Optional_ | Which page to load by default, and on the base page or domain root. You can still switch to different views from within the UI. Can be either `default`, `minimal` or `workspace`. Defaults to `default`
**`defaultOpeningMethod`** | `enum` | _Optional_ | The default opening method for items, if no `target` is specified for a given item. Can be either `newtab`, `sametab`, `top`, `parent`, `modal` or `workspace`. Defaults to `newtab`
**`statusCheck`** | `boolean` | _Optional_ | When set to `true`, Dashy will ping each of your services and display their status as a dot next to each item. This can be overridden by setting `statusCheck` under each item. Defaults to `false`
**`statusCheckInterval`** | `boolean` | _Optional_ | The number of seconds between checks. If set to `0` then service will only be checked on initial page load, which is usually the desired functionality. If value is less than `10` you may experience a hit in performance. Defaults to `0`
**`webSearch`** | `object` | _Optional_ | Configuration options for the web search feature, set your default search engine, opening method or disable web search. See [`webSearch`](#appconfigwebsearch-optional)
@ -181,7 +182,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
**`description`** | `string` | _Optional_ | Additional info about an item, which is shown in the tooltip on hover, or visible on large tiles
**`url`** | `string` | Required | The URL / location of web address for when the item is clicked
**`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.
**`target`** | `string` | _Optional_ | The opening method for when the item is clicked, either `newtab`, `sametab`, `top`, `parent`, `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 and `workspace` will open in the Workspace view. Defaults to `newtab`
**`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`

View File

@ -7,7 +7,7 @@ One of the primary purposes of Dashy is to allow you to quickly find and launch
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.
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`, `top` 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.

View File

@ -1,6 +1,6 @@
{
"name": "Dashy",
"version": "1.8.7",
"version": "1.8.8",
"license": "MIT",
"main": "server",
"scripts": {

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="level-up" class="svg-inline--fa fa-level-up fa-w-11" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M345.04 144l-136-136.901c-9.388-9.465-24.691-9.465-34.079 0L38.96 144c-9.307 9.384-9.277 24.526.069 33.872l22.056 22.056c9.619 9.619 25.301 9.329 34.557-.639L152 138.84V432H68.024a11.996 11.996 0 0 0-8.485 3.515l-56 56C-4.021 499.074 1.333 512 12.024 512H208c13.255 0 24-10.745 24-24V138.84l56.357 60.448c9.256 9.968 24.938 10.258 34.557.639l22.056-22.056c9.346-9.345 9.377-24.487.07-33.871z"></path></svg>

After

Width:  |  Height:  |  Size: 627 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="box-open" class="svg-inline--fa fa-box-open fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M638.3 143.8L586.8 41c-4-8-12.1-9.5-16.7-8.9L320 64 69.8 32.1c-4.6-.6-12.6.9-16.6 8.9L1.7 143.8c-4.6 9.2.3 20.2 10.1 23L64 181.7V393c0 14.7 10 27.5 24.2 31l216.2 54.1c6 1.5 17.4 3.4 31 0L551.8 424c14.2-3.6 24.2-16.4 24.2-31V181.7l52.1-14.9c9.9-2.8 14.7-13.8 10.2-23zM86 82.6l154.8 19.7-41.2 68.3-138-39.4L86 82.6zm26 112.8l97.8 27.9c8 2.3 15.2-1.8 18.5-7.3L296 103.8v322.7l-184-46V195.4zm416 185.1l-184 46V103.8l67.7 112.3c3.3 5.5 10.6 9.6 18.5 7.3l97.8-27.9v185zm-87.7-209.9l-41.2-68.3L554 82.6l24.3 48.6-138 39.4z"></path></svg>

After

Width:  |  Height:  |  Size: 751 B

View File

@ -1,20 +1 @@
<svg
aria-hidden="true"
focusable="false"
data-prefix="far"
data-icon="browser"
class="svg-inline--fa fa-browser fa-w-16"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
transform = "rotate(-90 250 250)"
fill="currentColor"
d="M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5
48-48V80c0-26.5-21.5-48-48-48zM48 92c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12
12v24c0 6.6-5.4 12-12 12H60c-6.6 0-12-5.4-12-12V92zm416 334c0 3.3-2.7 6-6
6H54c-3.3 0-6-2.7-6-6V168h416v258zm0-310c0 6.6-5.4 12-12 12H172c-6.6
0-12-5.4-12-12V92c0-6.6 5.4-12 12-12h280c6.6 0 12 5.4 12 12v24z">
</path>
</svg>
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="briefcase" class="svg-inline--fa fa-briefcase fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 128h-80V80c0-26.51-21.49-48-48-48H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v256c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V176c0-26.51-21.49-48-48-48zM176 80h160v48H176V80zM54 176h404c3.31 0 6 2.69 6 6v74H48v-74c0-3.31 2.69-6 6-6zm404 256H54c-3.31 0-6-2.69-6-6V304h144v24c0 13.25 10.75 24 24 24h80c13.25 0 24-10.75 24-24v-24h144v122c0 3.31-2.69 6-6 6z"></path></svg>

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 617 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="question" class="svg-inline--fa fa-question fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M202.021 0C122.202 0 70.503 32.703 29.914 91.026c-7.363 10.58-5.093 25.086 5.178 32.874l43.138 32.709c10.373 7.865 25.132 6.026 33.253-4.148 25.049-31.381 43.63-49.449 82.757-49.449 30.764 0 68.816 19.799 68.816 49.631 0 22.552-18.617 34.134-48.993 51.164-35.423 19.86-82.299 44.576-82.299 106.405V320c0 13.255 10.745 24 24 24h72.471c13.255 0 24-10.745 24-24v-5.773c0-42.86 125.268-44.645 125.268-160.627C377.504 66.256 286.902 0 202.021 0zM192 373.459c-38.196 0-69.271 31.075-69.271 69.271 0 38.195 31.075 69.27 69.271 69.27s69.271-31.075 69.271-69.271-31.075-69.27-69.271-69.27z"></path></svg>

After

Width:  |  Height:  |  Size: 816 B

View File

@ -156,7 +156,7 @@ export default {
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(data.pageInfo));
}
if (data.appConfig) {
data.appConfig.auth = this.config.appConfig.auth || [];
data.appConfig.auth = this.config.appConfig.auth || {};
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(data.appConfig));
}
if (data.appConfig.theme) {

View File

@ -3,8 +3,8 @@
<a @click="itemOpened"
@mouseup.right="openContextMenu"
@contextmenu.prevent
:href="(target !== 'modal' && target !== 'workspace') ? url : '#'"
:target="target === 'newtab' ? '_blank' : ''"
:href="hyperLinkHref"
:target="anchorTarget"
:class="`item ${!icon? 'short': ''} size-${itemSize}`"
v-tooltip="getTooltipOptions()"
rel="noopener noreferrer" tabindex="0"
@ -21,7 +21,7 @@
v-bind:style="customStyles" class="bounce" />
<!-- Small icon, showing opening method on hover -->
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon || itemSize === 'small'"
:openingMethod="target" position="bottom right"
:openingMethod="accumulatedTarget" position="bottom right"
:hotkey="hotkey" />
<!-- Status indicator dot (if enabled) showing weather srevice is availible -->
<StatusIndicator
@ -49,7 +49,12 @@ import Icon from '@/components/LinkItems/ItemIcon.vue';
import ItemOpenMethodIcon from '@/components/LinkItems/ItemOpenMethodIcon';
import StatusIndicator from '@/components/LinkItems/StatusIndicator';
import ContextMenu from '@/components/LinkItems/ContextMenu';
import { localStorageKeys, serviceEndpoints } from '@/utils/defaults';
import {
localStorageKeys,
serviceEndpoints,
openingMethod as defaultOpeningMethod,
} from '@/utils/defaults';
import { targetValidator } from '@/utils/ConfigHelpers';
export default {
name: 'Item',
@ -66,8 +71,7 @@ export default {
hotkey: Number, // Shortcut for quickly launching app
target: { // Where resource will open, either 'newtab', 'sametab' or 'modal'
type: String,
default: 'newtab',
validator: (value) => ['newtab', 'sametab', 'modal', 'workspace'].indexOf(value) !== -1,
validator: targetValidator,
},
itemSize: String,
enableStatusCheck: Boolean,
@ -80,6 +84,25 @@ export default {
appConfig() {
return this.$store.getters.appConfig;
},
accumulatedTarget() {
return this.target || this.appConfig.defaultOpeningMethod || defaultOpeningMethod;
},
/* Convert config target value, into HTML anchor target attribute */
anchorTarget() {
const target = this.accumulatedTarget;
switch (target) {
case 'sametab': return '_self';
case 'newtab': return '_blank';
case 'parent': return '_parent';
case 'top': return '_top';
default: return undefined;
}
},
/* Get the href value for the anchor, if not opening in modal/ workspace */
hyperLinkHref() {
const noAnchorNeeded = ['modal', 'workspace'];
return noAnchorNeeded.includes(this.accumulatedTarget) ? '#' : this.url;
},
},
data() {
return {
@ -105,10 +128,10 @@ export default {
methods: {
/* Called when an item is clicked, manages the opening of modal & resets the search field */
itemOpened(e) {
if (e.altKey || this.target === 'modal') {
if (e.altKey || this.accumulatedTarget === 'modal') {
e.preventDefault();
this.$emit('triggerModal', this.url);
} else if (this.target === 'workspace') {
} else if (this.accumulatedTarget === 'workspace') {
router.push({ name: 'workspace', query: { url: this.url } });
} else {
this.$emit('itemClicked');
@ -151,12 +174,15 @@ export default {
classes: `item-description-tooltip tooltip-is-${this.itemSize}`,
};
},
/* Used by certain themes, which display an icon with animated CSS */
/* Used by certain themes (material), to show animated CSS icon */
getUnicodeOpeningIcon() {
switch (this.target) {
switch (this.accumulatedTarget) {
case 'newtab': return '"\\f360"';
case 'sametab': return '"\\f24d"';
case 'parent': return '"\\f3bf"';
case 'top': return '"\\f102"';
case 'modal': return '"\\f2d0"';
case 'workspace': return '"\\f0b1"';
default: return '"\\f054"';
}
},

View File

@ -5,6 +5,9 @@
<SameTabOpenIcon v-else-if="openingMethod === 'sametab'" />
<IframeOpenIcon v-else-if="openingMethod === 'modal'" />
<WorkspaceOpenIcon v-else-if="openingMethod === 'workspace'" />
<ParentOpenIcon v-else-if="openingMethod === 'parent'" />
<TopOpenIcon v-else-if="openingMethod === 'top'" />
<UnknownIcon v-else />
</div>
<div v-if="hotkey" :class="`hotkey-denominator ${makeClass(position, isSmall, isTransparent)}`">
{{ hotkey }}
@ -20,11 +23,14 @@ import NewTabOpenIcon from '@/assets/interface-icons/open-new-tab.svg';
import SameTabOpenIcon from '@/assets/interface-icons/open-current-tab.svg';
import IframeOpenIcon from '@/assets/interface-icons/open-iframe.svg';
import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg';
import ParentOpenIcon from '@/assets/interface-icons/open-parent.svg';
import TopOpenIcon from '@/assets/interface-icons/open-top.svg';
import UnknownIcon from '@/assets/interface-icons/unknown-icon.svg';
export default {
name: 'ItemOpenMethodIcon',
props: {
openingMethod: String, // newtab | sametab | modal | workspace
openingMethod: String, // newtab | sametab | parent | top | modal | workspace
isSmall: Boolean, // If true, will apply small class
position: String, // Position classes: top, bottom, left, right
isTransparent: Boolean, // If true, will apply opacity
@ -44,6 +50,9 @@ export default {
SameTabOpenIcon,
IframeOpenIcon,
WorkspaceOpenIcon,
ParentOpenIcon,
TopOpenIcon,
UnknownIcon,
},
};
</script>

View File

@ -7,6 +7,8 @@ import {
theme as defaultTheme,
language as defaultLanguage,
} from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
import ConfigSchema from '@/utils/ConfigSchema.json';
/**
* Initiates the Accumulator class and generates a complete config object
@ -97,3 +99,17 @@ export const getUsersLanguage = () => {
const langObj = languages.find(lang => lang.code === langCode);
return langObj;
};
/**
* validator for item target attribute
* Uses enum values from config schema, and shows warning if invalid
* @param {String} target
* @returns {Boolean} isValid
*/
export const targetValidator = (target) => {
const acceptedTargets = ConfigSchema.properties.sections.items
.properties.items.items.properties.target.enum;
const isTargetValid = acceptedTargets.indexOf(target) !== -1;
if (!isTargetValid) ErrorHandler(`Unknown target value: ${target}`);
return isTargetValid;
};

View File

@ -76,6 +76,18 @@
"default": "default",
"description": "Which page to load by default, and on the base page or domain root. You can still switch to different views from within the UI"
},
"defaultOpeningMethod": {
"enum": [
"newtab",
"sametab",
"parent",
"top",
"modal",
"workspace"
],
"default": "newtab",
"description": "The default opening method for items. Only used if no item.target is specified"
},
"theme": {
"type": "string",
"default": "callisto",
@ -554,6 +566,8 @@
"enum": [
"newtab",
"sametab",
"parent",
"top",
"modal",
"workspace"
],

View File

@ -139,6 +139,8 @@ module.exports = {
metaTagData: [
{ name: 'description', content: 'A simple static homepage for you\'re server' },
],
/* If no 'target' specified, this is the default opening method */
openingMethod: 'newtab',
/* Default option for Toast messages */
toastedOptions: {
position: 'bottom-center',