mirror of
https://github.com/eugeny/tabby
synced 2026-07-08 16:11:00 +00:00
Make keyboard-interactive auth URLs clickable (#11327)
Co-authored-by: pratyushjaiswal0806-dot <pratyushjaiswal0806@gmail.com>
This commit is contained in:
@@ -2,7 +2,15 @@
|
||||
strong(translate) Keyboard-interactive auth
|
||||
.ms-2 {{prompt.name}}
|
||||
|
||||
.prompt-text {{prompt.prompts[step].prompt}}
|
||||
.prompt-text
|
||||
span(*ngFor='let part of getPromptParts()')
|
||||
a(
|
||||
*ngIf='part.url',
|
||||
[href]='part.url',
|
||||
rel='noreferrer noopener',
|
||||
(click)='openPromptLink(part.url, $event)'
|
||||
) {{part.text}}
|
||||
span(*ngIf='!part.url') {{part.text}}
|
||||
|
||||
input.form-control.mt-2(
|
||||
#input,
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core'
|
||||
import { PlatformService } from 'tabby-core'
|
||||
import { KeyboardInteractivePrompt } from '../session/ssh'
|
||||
import { SSHProfile } from '../api'
|
||||
import { PasswordStorageService } from '../services/passwordStorage.service'
|
||||
|
||||
const PROMPT_URL_REGEX = /https?:\/\/[^\s<>"']+/g
|
||||
const TRAILING_PROMPT_URL_PUNCTUATION = /[),.;:!?]+$/
|
||||
|
||||
interface PromptPart {
|
||||
text: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'keyboard-interactive-auth-panel',
|
||||
templateUrl: './keyboardInteractiveAuthPanel.component.pug',
|
||||
@@ -19,6 +28,7 @@ export class KeyboardInteractiveAuthComponent implements OnInit {
|
||||
|
||||
constructor (
|
||||
private passwordStorage: PasswordStorageService,
|
||||
private platform: PlatformService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
@@ -42,6 +52,52 @@ export class KeyboardInteractiveAuthComponent implements OnInit {
|
||||
return this.prompt.prompts[this.step].echo ?? false
|
||||
}
|
||||
|
||||
getPromptParts (): PromptPart[] {
|
||||
return this.parsePromptText(this.prompt.prompts[this.step].prompt)
|
||||
}
|
||||
|
||||
parsePromptText (text: string): PromptPart[] {
|
||||
const parts: PromptPart[] = []
|
||||
let lastIndex = 0
|
||||
|
||||
for (const match of text.matchAll(PROMPT_URL_REGEX)) {
|
||||
const matchedText = match[0]
|
||||
const matchIndex = match.index ?? 0
|
||||
const punctuationMatch = TRAILING_PROMPT_URL_PUNCTUATION.exec(matchedText)
|
||||
const trailingPunctuation = punctuationMatch?.[0] ?? ''
|
||||
const url = trailingPunctuation ? matchedText.slice(0, -trailingPunctuation.length) : matchedText
|
||||
|
||||
if (matchIndex > lastIndex) {
|
||||
parts.push({ text: text.slice(lastIndex, matchIndex) })
|
||||
}
|
||||
|
||||
if (this.isPromptUrl(url)) {
|
||||
parts.push({ text: url, url })
|
||||
if (trailingPunctuation) {
|
||||
parts.push({ text: trailingPunctuation })
|
||||
}
|
||||
} else {
|
||||
parts.push({ text: matchedText })
|
||||
}
|
||||
|
||||
lastIndex = matchIndex + matchedText.length
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
parts.push({ text: text.slice(lastIndex) })
|
||||
}
|
||||
|
||||
return parts.length ? parts : [{ text }]
|
||||
}
|
||||
|
||||
openPromptLink (url: string|undefined, event: Event): void {
|
||||
event.preventDefault()
|
||||
|
||||
if (url) {
|
||||
this.platform.openExternal(url)
|
||||
}
|
||||
}
|
||||
|
||||
previous (): void {
|
||||
if (this.step > 0) {
|
||||
this.step--
|
||||
@@ -62,4 +118,13 @@ export class KeyboardInteractiveAuthComponent implements OnInit {
|
||||
this.step++
|
||||
this.input.nativeElement.focus()
|
||||
}
|
||||
|
||||
private isPromptUrl (url: string): boolean {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user