mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
feat(links): new external link implementation
This commit is contained in:
@@ -322,6 +322,7 @@ import omit from 'lodash/omit';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
||||
import closeChallengeModal from './closeChallengeModal';
|
||||
import Column from '../tasks/column';
|
||||
@@ -358,7 +359,7 @@ export default {
|
||||
userLink,
|
||||
groupLink,
|
||||
},
|
||||
mixins: [challengeMemberSearchMixin, userStateMixin],
|
||||
mixins: [challengeMemberSearchMixin, externalLinks, userStateMixin],
|
||||
props: ['challengeId'],
|
||||
data () {
|
||||
return {
|
||||
@@ -414,6 +415,10 @@ export default {
|
||||
mounted () {
|
||||
if (!this.searchId) this.searchId = this.challengeId;
|
||||
if (!this.challenge._id) this.loadChallenge();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
async beforeRouteUpdate (to, from, next) {
|
||||
this.searchId = to.params.challengeId;
|
||||
|
||||
@@ -120,6 +120,7 @@ import { mapState } from '@/libs/store';
|
||||
import Sidebar from './sidebar';
|
||||
import ChallengeItem from './challengeItem';
|
||||
import challengeModal from './challengeModal';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
@@ -131,7 +132,7 @@ export default {
|
||||
challengeModal,
|
||||
MugenScroll,
|
||||
},
|
||||
mixins: [challengeUtilities],
|
||||
mixins: [challengeUtilities, externalLinks],
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
@@ -177,6 +178,10 @@ export default {
|
||||
section: this.$t('challenges'),
|
||||
});
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
updateSearch (eventData) {
|
||||
|
||||
@@ -81,6 +81,8 @@ import challengeModal from './challengeModal';
|
||||
import { mapState } from '@/libs/store';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import challengeItem from './challengeItem';
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
|
||||
@@ -92,6 +94,7 @@ export default {
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
props: ['group'],
|
||||
data () {
|
||||
return {
|
||||
@@ -118,6 +121,10 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
async loadChallenges () {
|
||||
|
||||
@@ -145,6 +145,7 @@ import Sidebar from './sidebar';
|
||||
import ChallengeItem from './challengeItem';
|
||||
import challengeModal from './challengeModal';
|
||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
|
||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
@@ -156,7 +157,7 @@ export default {
|
||||
challengeModal,
|
||||
MugenScroll,
|
||||
},
|
||||
mixins: [challengeUtilities],
|
||||
mixins: [challengeUtilities, externalLinks],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
@@ -203,6 +204,10 @@ export default {
|
||||
section: this.$t('challenges'),
|
||||
});
|
||||
this.loadChallenges();
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
updateSearch (eventData) {
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import externalLinks from '../../mixins/externalLinks';
|
||||
|
||||
import autocomplete from '../chat/autoComplete';
|
||||
import communityGuidelines from './communityGuidelines';
|
||||
import chatMessage from '../chat/chatMessages';
|
||||
@@ -103,6 +105,7 @@ export default {
|
||||
communityGuidelines,
|
||||
chatMessage,
|
||||
},
|
||||
mixins: [externalLinks],
|
||||
props: ['label', 'group', 'placeholder'],
|
||||
data () {
|
||||
return {
|
||||
@@ -133,6 +136,9 @@ export default {
|
||||
mounted () {
|
||||
this.textbox = this.$refs['user-entry'];
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
methods: {
|
||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||
getCoord (e, text) {
|
||||
|
||||
@@ -355,6 +355,7 @@ import Task from './task';
|
||||
import ClearCompletedTodos from './clearCompletedTodos';
|
||||
import buyMixin from '@/mixins/buy';
|
||||
import sync from '@/mixins/sync';
|
||||
import externalLinks from '@/mixins/externalLinks';
|
||||
import { mapState, mapActions, mapGetters } from '@/libs/store';
|
||||
import shopItem from '../shops/shopItem';
|
||||
import BuyQuestModal from '@/components/shops/quests/buyQuestModal.vue';
|
||||
@@ -384,7 +385,7 @@ export default {
|
||||
shopItem,
|
||||
draggable,
|
||||
},
|
||||
mixins: [buyMixin, notifications, sync],
|
||||
mixins: [buyMixin, notifications, sync, externalLinks],
|
||||
// @TODO Set default values for props
|
||||
// allows for better control of props values
|
||||
// allows for better control of where this component is called
|
||||
@@ -534,6 +535,10 @@ export default {
|
||||
if (this.activeFilter.label !== 'complete2') return;
|
||||
this.loadCompletedTodos();
|
||||
});
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
updated () {
|
||||
this.handleExternalLinks();
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('buyModal::boughtItem');
|
||||
|
||||
@@ -9,21 +9,5 @@ export default function markdown (el, { value, oldValue }) {
|
||||
el.innerHTML = '';
|
||||
}
|
||||
|
||||
const allLinks = el.getElementsByTagName('a');
|
||||
|
||||
for (let i = 0; i < allLinks.length; i += 1) {
|
||||
const link = allLinks[i];
|
||||
|
||||
link.addEventListener('click', e => {
|
||||
if (e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
window.externalLink(link.href);
|
||||
});
|
||||
}
|
||||
|
||||
el.classList.add('markdown');
|
||||
}
|
||||
|
||||
38
website/client/src/mixins/externalLinks.js
Normal file
38
website/client/src/mixins/externalLinks.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import some from 'lodash/some';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
trustedDomains: [
|
||||
'https://habitica.com',
|
||||
'http://localhost',
|
||||
'https://tools.habitica.com',
|
||||
'https://translate.habitica.com',
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleExternalLinks () {
|
||||
const allLinks = document.getElementsByTagName('a');
|
||||
|
||||
for (let i = 0; i < allLinks.length; i += 1) {
|
||||
const link = allLinks[i];
|
||||
|
||||
if ((link.classList.value.indexOf('external-link') === -1)
|
||||
&& link.href.slice(0, 4) === 'http'
|
||||
&& !some(this.trustedDomains, domain => link.href.indexOf(domain) === 0)) {
|
||||
link.classList.add('external-link');
|
||||
link.addEventListener('click', e => {
|
||||
if (e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
window.externalLink(link.href);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user