mirror of
https://github.com/m1k1o/neko.git
synced 2026-11-03 00:11:48 -06:00
feat: add openinapp plugin to open chat links in shared neko instance (#682)
* feat: add openinapp plugin to open chat links in shared neko instance * lint. * feat(openinapp): implement open link handler and route for opening links. * feat(openinapp): switched to http backend and removed example in docker-compose.yaml * lint. --------- Co-authored-by: Miroslav Šedivý <sedivy.miro@gmail.com>
This commit is contained in:
@@ -18,7 +18,11 @@
|
||||
<span>{{ member(message.id).displayname }}</span>
|
||||
<span class="timestamp">{{ timestamp(message.created) }}</span>
|
||||
</div>
|
||||
<neko-markdown class="content-body" :source="message.content" />
|
||||
<neko-markdown
|
||||
class="content-body"
|
||||
:source="message.content"
|
||||
:open-in-app="$accessor.remote.hosting && $accessor.openinapp.enabled"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li :key="index" class="event" v-if="message.type === 'event'">
|
||||
@@ -225,6 +229,11 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.open-in-app {
|
||||
margin-left: 0.3em;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,6 +469,27 @@
|
||||
target.parentElement.classList.add('active')
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
if (target.classList.contains('open-in-app')) {
|
||||
const href = target.dataset.href
|
||||
if (href) {
|
||||
this.$accessor.openinapp.sendOpenLink(href)
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
if (
|
||||
this.$accessor.settings.links_in_app &&
|
||||
this.$accessor.openinapp.enabled &&
|
||||
this.$accessor.remote.hosting &&
|
||||
target.tagName.toLowerCase() === 'a'
|
||||
) {
|
||||
const href = target.getAttribute('href')
|
||||
if (href) {
|
||||
this.$accessor.openinapp.sendOpenLink(href)
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown(event: KeyboardEvent) {
|
||||
|
||||
@@ -136,12 +136,21 @@ const rules: MarkdownRules = {
|
||||
}
|
||||
},
|
||||
html(node, output, state) {
|
||||
return htmlTag(
|
||||
const href = md.sanitizeUrl(node.target) as string
|
||||
const link = htmlTag(
|
||||
'a',
|
||||
output(node.content, state),
|
||||
{ href: md.sanitizeUrl(node.target) as string, target: '_blank' },
|
||||
state,
|
||||
)
|
||||
if (!state.openInApp) return link
|
||||
const inAppIcon = htmlTag(
|
||||
'i',
|
||||
'',
|
||||
{ class: 'open-in-app fas fa-arrow-up-right-from-square', 'data-href': href },
|
||||
state,
|
||||
)
|
||||
return link + inAppIcon
|
||||
},
|
||||
},
|
||||
url: {
|
||||
@@ -158,12 +167,21 @@ const rules: MarkdownRules = {
|
||||
}
|
||||
},
|
||||
html(node, output, state) {
|
||||
return htmlTag(
|
||||
const href = md.sanitizeUrl(node.target) as string
|
||||
const link = htmlTag(
|
||||
'a',
|
||||
output(node.content, state),
|
||||
{ href: md.sanitizeUrl(node.target) as string, target: '_blank' },
|
||||
state,
|
||||
)
|
||||
if (!state.openInApp) return link
|
||||
const inAppIcon = htmlTag(
|
||||
'i',
|
||||
'',
|
||||
{ class: 'open-in-app fas fa-arrow-right-to-bracket', 'data-href': href },
|
||||
state,
|
||||
)
|
||||
return link + inAppIcon
|
||||
},
|
||||
},
|
||||
strike: {
|
||||
@@ -257,12 +275,16 @@ export default class extends Vue {
|
||||
@Prop({ required: true })
|
||||
source!: string
|
||||
|
||||
@Prop({ default: false })
|
||||
openInApp!: boolean
|
||||
|
||||
render(h: any) {
|
||||
const state: MarkdownState = {
|
||||
inline: true,
|
||||
inQuote: false,
|
||||
escapeHTML: true,
|
||||
cssModuleNames: null,
|
||||
openInApp: this.openInApp,
|
||||
}
|
||||
return h({ template: `<div>${htmlOutput(parser(this.source, state), state)}</div>` })
|
||||
}
|
||||
|
||||
@@ -35,6 +35,13 @@
|
||||
<span />
|
||||
</label>
|
||||
</li>
|
||||
<li v-if="$accessor.openinapp.enabled">
|
||||
<span>Always open Links in App</span>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="links_in_app" />
|
||||
<span />
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<span>{{ $t('setting.keyboard_layout') }}</span>
|
||||
<label class="select">
|
||||
@@ -358,6 +365,14 @@
|
||||
this.$accessor.settings.setSound(value)
|
||||
}
|
||||
|
||||
get links_in_app() {
|
||||
return this.$accessor.settings.links_in_app
|
||||
}
|
||||
|
||||
set links_in_app(value: boolean) {
|
||||
this.$accessor.settings.setLinksInApp(value)
|
||||
}
|
||||
|
||||
get keyboard_layouts_list() {
|
||||
return this.$accessor.settings.keyboard_layouts_list
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ export const EVENT = {
|
||||
LIST: 'filetransfer/list',
|
||||
REFRESH: 'filetransfer/refresh',
|
||||
},
|
||||
OPENINAPP: {
|
||||
INIT: 'openinapp/init',
|
||||
OPENLINK: 'openinapp/openlink',
|
||||
},
|
||||
SCREEN: {
|
||||
CONFIGURATIONS: 'screen/configurations',
|
||||
RESOLUTION: 'screen/resolution',
|
||||
@@ -78,6 +82,7 @@ export type WebSocketEvents =
|
||||
| SignalEvents
|
||||
| ChatEvents
|
||||
| FileTransferEvents
|
||||
| OpenInAppEvents
|
||||
| ScreenEvents
|
||||
| BroadcastEvents
|
||||
| AdminEvents
|
||||
@@ -104,6 +109,8 @@ export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE
|
||||
|
||||
export type FileTransferEvents = typeof EVENT.FILETRANSFER.LIST | typeof EVENT.FILETRANSFER.REFRESH
|
||||
|
||||
export type OpenInAppEvents = typeof EVENT.OPENINAPP.INIT | typeof EVENT.OPENINAPP.OPENLINK
|
||||
|
||||
export type ScreenEvents = typeof EVENT.SCREEN.CONFIGURATIONS | typeof EVENT.SCREEN.RESOLUTION | typeof EVENT.SCREEN.SET
|
||||
|
||||
export type BroadcastEvents =
|
||||
|
||||
@@ -375,6 +375,13 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
||||
this.$accessor.files.setUserDelete(user_delete)
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// Open in App Events
|
||||
/////////////////////////////
|
||||
protected [EVENT.OPENINAPP.INIT]({ enabled }: { enabled: boolean }) {
|
||||
this.$accessor.openinapp.setEnabled(enabled)
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// Screen Events
|
||||
/////////////////////////////
|
||||
|
||||
@@ -8,6 +8,7 @@ import { get, set } from '~/utils/localstorage'
|
||||
import * as video from './video'
|
||||
import * as chat from './chat'
|
||||
import * as files from './files'
|
||||
import * as openinapp from './openinapp'
|
||||
import * as remote from './remote'
|
||||
import * as user from './user'
|
||||
import * as settings from './settings'
|
||||
@@ -111,7 +112,7 @@ export const storePattern = {
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
modules: { video, chat, files, user, remote, settings, client, emoji },
|
||||
modules: { video, chat, files, openinapp, user, remote, settings, client, emoji },
|
||||
}
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
||||
import { EVENT } from '~/neko/events'
|
||||
import { accessor } from '~/store'
|
||||
|
||||
export const namespaced = true
|
||||
|
||||
export const state = () => ({
|
||||
enabled: false,
|
||||
})
|
||||
|
||||
export const getters = getterTree(state, {
|
||||
//
|
||||
})
|
||||
|
||||
export const mutations = mutationTree(state, {
|
||||
setEnabled(state, enabled: boolean) {
|
||||
state.enabled = enabled
|
||||
},
|
||||
})
|
||||
|
||||
export const actions = actionTree(
|
||||
{ state, getters, mutations },
|
||||
{
|
||||
sendOpenLink(store, url: string) {
|
||||
if (!accessor.connected) return
|
||||
$client.sendMessage(EVENT.OPENINAPP.OPENLINK, { text: url })
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -16,6 +16,7 @@ export const state = () => {
|
||||
autoplay: get<boolean>('autoplay', true),
|
||||
ignore_emotes: get<boolean>('ignore_emotes', false),
|
||||
chat_sound: get<boolean>('chat_sound', true),
|
||||
links_in_app: get<boolean>('links_in_app', false),
|
||||
keyboard_layout: get<string>('keyboard_layout', 'us'),
|
||||
|
||||
keyboard_layouts_list: {} as KeyboardLayouts,
|
||||
@@ -53,6 +54,11 @@ export const mutations = mutationTree(state, {
|
||||
set('chat_sound', value)
|
||||
},
|
||||
|
||||
setLinksInApp(state, value: boolean) {
|
||||
state.links_in_app = value
|
||||
set('links_in_app', value)
|
||||
},
|
||||
|
||||
setKeyboardLayout(state, value: string) {
|
||||
state.keyboard_layout = value
|
||||
set('keyboard_layout', value)
|
||||
|
||||
Reference in New Issue
Block a user