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 { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { userStateMixin } from '../../mixins/userState';
|
import { userStateMixin } from '../../mixins/userState';
|
||||||
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
||||||
import closeChallengeModal from './closeChallengeModal';
|
import closeChallengeModal from './closeChallengeModal';
|
||||||
import Column from '../tasks/column';
|
import Column from '../tasks/column';
|
||||||
@@ -358,7 +359,7 @@ export default {
|
|||||||
userLink,
|
userLink,
|
||||||
groupLink,
|
groupLink,
|
||||||
},
|
},
|
||||||
mixins: [challengeMemberSearchMixin, userStateMixin],
|
mixins: [challengeMemberSearchMixin, externalLinks, userStateMixin],
|
||||||
props: ['challengeId'],
|
props: ['challengeId'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -414,6 +415,10 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
if (!this.searchId) this.searchId = this.challengeId;
|
if (!this.searchId) this.searchId = this.challengeId;
|
||||||
if (!this.challenge._id) this.loadChallenge();
|
if (!this.challenge._id) this.loadChallenge();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
async beforeRouteUpdate (to, from, next) {
|
async beforeRouteUpdate (to, from, next) {
|
||||||
this.searchId = to.params.challengeId;
|
this.searchId = to.params.challengeId;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ import { mapState } from '@/libs/store';
|
|||||||
import Sidebar from './sidebar';
|
import Sidebar from './sidebar';
|
||||||
import ChallengeItem from './challengeItem';
|
import ChallengeItem from './challengeItem';
|
||||||
import challengeModal from './challengeModal';
|
import challengeModal from './challengeModal';
|
||||||
|
import externalLinks from '@/mixins/externalLinks';
|
||||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||||
|
|
||||||
import positiveIcon from '@/assets/svg/positive.svg';
|
import positiveIcon from '@/assets/svg/positive.svg';
|
||||||
@@ -131,7 +132,7 @@ export default {
|
|||||||
challengeModal,
|
challengeModal,
|
||||||
MugenScroll,
|
MugenScroll,
|
||||||
},
|
},
|
||||||
mixins: [challengeUtilities],
|
mixins: [challengeUtilities, externalLinks],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -177,6 +178,10 @@ export default {
|
|||||||
section: this.$t('challenges'),
|
section: this.$t('challenges'),
|
||||||
});
|
});
|
||||||
this.loadChallenges();
|
this.loadChallenges();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSearch (eventData) {
|
updateSearch (eventData) {
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ import challengeModal from './challengeModal';
|
|||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import markdownDirective from '@/directives/markdown';
|
import markdownDirective from '@/directives/markdown';
|
||||||
|
|
||||||
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
|
|
||||||
import challengeItem from './challengeItem';
|
import challengeItem from './challengeItem';
|
||||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||||
|
|
||||||
@@ -92,6 +94,7 @@ export default {
|
|||||||
directives: {
|
directives: {
|
||||||
markdown: markdownDirective,
|
markdown: markdownDirective,
|
||||||
},
|
},
|
||||||
|
mixins: [externalLinks],
|
||||||
props: ['group'],
|
props: ['group'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -118,6 +121,10 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.loadChallenges();
|
this.loadChallenges();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadChallenges () {
|
async loadChallenges () {
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ import Sidebar from './sidebar';
|
|||||||
import ChallengeItem from './challengeItem';
|
import ChallengeItem from './challengeItem';
|
||||||
import challengeModal from './challengeModal';
|
import challengeModal from './challengeModal';
|
||||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||||
|
import externalLinks from '@/mixins/externalLinks';
|
||||||
|
|
||||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||||
import positiveIcon from '@/assets/svg/positive.svg';
|
import positiveIcon from '@/assets/svg/positive.svg';
|
||||||
@@ -156,7 +157,7 @@ export default {
|
|||||||
challengeModal,
|
challengeModal,
|
||||||
MugenScroll,
|
MugenScroll,
|
||||||
},
|
},
|
||||||
mixins: [challengeUtilities],
|
mixins: [challengeUtilities, externalLinks],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
@@ -203,6 +204,10 @@ export default {
|
|||||||
section: this.$t('challenges'),
|
section: this.$t('challenges'),
|
||||||
});
|
});
|
||||||
this.loadChallenges();
|
this.loadChallenges();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSearch (eventData) {
|
updateSearch (eventData) {
|
||||||
|
|||||||
@@ -87,6 +87,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
|
|
||||||
import autocomplete from '../chat/autoComplete';
|
import autocomplete from '../chat/autoComplete';
|
||||||
import communityGuidelines from './communityGuidelines';
|
import communityGuidelines from './communityGuidelines';
|
||||||
import chatMessage from '../chat/chatMessages';
|
import chatMessage from '../chat/chatMessages';
|
||||||
@@ -103,6 +105,7 @@ export default {
|
|||||||
communityGuidelines,
|
communityGuidelines,
|
||||||
chatMessage,
|
chatMessage,
|
||||||
},
|
},
|
||||||
|
mixins: [externalLinks],
|
||||||
props: ['label', 'group', 'placeholder'],
|
props: ['label', 'group', 'placeholder'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -133,6 +136,9 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
this.textbox = this.$refs['user-entry'];
|
this.textbox = this.$refs['user-entry'];
|
||||||
},
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||||
getCoord (e, text) {
|
getCoord (e, text) {
|
||||||
|
|||||||
@@ -355,6 +355,7 @@ import Task from './task';
|
|||||||
import ClearCompletedTodos from './clearCompletedTodos';
|
import ClearCompletedTodos from './clearCompletedTodos';
|
||||||
import buyMixin from '@/mixins/buy';
|
import buyMixin from '@/mixins/buy';
|
||||||
import sync from '@/mixins/sync';
|
import sync from '@/mixins/sync';
|
||||||
|
import externalLinks from '@/mixins/externalLinks';
|
||||||
import { mapState, mapActions, mapGetters } from '@/libs/store';
|
import { mapState, mapActions, mapGetters } from '@/libs/store';
|
||||||
import shopItem from '../shops/shopItem';
|
import shopItem from '../shops/shopItem';
|
||||||
import BuyQuestModal from '@/components/shops/quests/buyQuestModal.vue';
|
import BuyQuestModal from '@/components/shops/quests/buyQuestModal.vue';
|
||||||
@@ -384,7 +385,7 @@ export default {
|
|||||||
shopItem,
|
shopItem,
|
||||||
draggable,
|
draggable,
|
||||||
},
|
},
|
||||||
mixins: [buyMixin, notifications, sync],
|
mixins: [buyMixin, notifications, sync, externalLinks],
|
||||||
// @TODO Set default values for props
|
// @TODO Set default values for props
|
||||||
// allows for better control of props values
|
// allows for better control of props values
|
||||||
// allows for better control of where this component is called
|
// allows for better control of where this component is called
|
||||||
@@ -534,6 +535,10 @@ export default {
|
|||||||
if (this.activeFilter.label !== 'complete2') return;
|
if (this.activeFilter.label !== 'complete2') return;
|
||||||
this.loadCompletedTodos();
|
this.loadCompletedTodos();
|
||||||
});
|
});
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
this.$root.$off('buyModal::boughtItem');
|
this.$root.$off('buyModal::boughtItem');
|
||||||
|
|||||||
@@ -9,21 +9,5 @@ export default function markdown (el, { value, oldValue }) {
|
|||||||
el.innerHTML = '';
|
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');
|
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