-
-
Couldn't load subscription status.
- Fork 6.2k
Fix "ref-issue" handling in markup #35739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,62 +2,53 @@ | |
| import {SvgIcon} from '../svg.ts'; | ||
| import {GET} from '../modules/fetch.ts'; | ||
| import {getIssueColor, getIssueIcon} from '../features/issue.ts'; | ||
| import {computed, onMounted, shallowRef, useTemplateRef} from 'vue'; | ||
| import type {IssuePathInfo} from '../types.ts'; | ||
| import {computed, onMounted, shallowRef} from 'vue'; | ||
|
|
||
| const {appSubUrl, i18n} = window.config; | ||
| const props = defineProps<{ | ||
| repoLink: string, | ||
| loadIssueInfoUrl: string, | ||
| }>(); | ||
|
|
||
| const loading = shallowRef(false); | ||
| const issue = shallowRef(null); | ||
| const renderedLabels = shallowRef(''); | ||
| const i18nErrorOccurred = i18n.error_occurred; | ||
| const i18nErrorMessage = shallowRef(null); | ||
| const errorMessage = shallowRef(null); | ||
|
|
||
| const createdAt = computed(() => new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'})); | ||
| const body = computed(() => { | ||
| const body = issue.value.body.replace(/\n+/g, ' '); | ||
| if (body.length > 85) { | ||
| return `${body.substring(0, 85)}…`; | ||
| } | ||
| return body; | ||
| const createdAt = computed(() => { | ||
| return new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'}); | ||
| }); | ||
|
|
||
| const root = useTemplateRef('root'); | ||
|
|
||
| onMounted(() => { | ||
| root.value.addEventListener('ce-load-context-popup', (e: CustomEventInit<IssuePathInfo>) => { | ||
| if (!loading.value && issue.value === null) { | ||
| load(e.detail); | ||
| } | ||
| }); | ||
| const body = computed(() => { | ||
| const body = issue.value.body.replace(/\n+/g, ' '); | ||
| return body.length > 85 ? `${body.substring(0, 85)}…` : body; | ||
| }); | ||
|
|
||
| async function load(issuePathInfo: IssuePathInfo) { | ||
| onMounted(async () => { | ||
| loading.value = true; | ||
| i18nErrorMessage.value = null; | ||
|
|
||
| errorMessage.value = null; | ||
| try { | ||
| const response = await GET(`${appSubUrl}/${issuePathInfo.ownerName}/${issuePathInfo.repoName}/issues/${issuePathInfo.indexString}/info`); // backend: GetIssueInfo | ||
| const respJson = await response.json(); | ||
| if (!response.ok) { | ||
| i18nErrorMessage.value = respJson.message ?? i18n.network_error; | ||
| const resp = await GET(props.loadIssueInfoUrl); | ||
| if (!resp.ok) { | ||
| errorMessage.value = resp.status ? resp.statusText : 'Unknown network error'; | ||
| return; | ||
| } | ||
| const respJson = await resp.json(); | ||
| issue.value = respJson.convertedIssue; | ||
| renderedLabels.value = respJson.renderedLabels; | ||
| } catch { | ||
| i18nErrorMessage.value = i18n.network_error; | ||
| } finally { | ||
| loading.value = false; | ||
| } | ||
| } | ||
| }); | ||
| </script> | ||
|
|
||
| <template> | ||
| <div ref="root"> | ||
| <div class="tw-p-4"> | ||
| <div v-if="loading" class="tw-h-12 tw-w-12 is-loading"/> | ||
| <div v-if="!loading && issue !== null" class="tw-flex tw-flex-col tw-gap-2"> | ||
| <div class="tw-text-12">{{ issue.repository.full_name }} on {{ createdAt }}</div> | ||
| <div v-else-if="issue" class="tw-flex tw-flex-col tw-gap-2"> | ||
| <div class="tw-text-12"> | ||
| <a :href="repoLink" class="muted">{{ issue.repository.full_name }}</a> | ||
| on {{ createdAt }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, ideally. But I think it doesn't need to be in this PR's scope. Handling i18n with datetime is also tricky, especially here it is done on frontend and mixes HTML elements. Leave it to the future (and I don't see anyone complains) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we dealt with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, relative-time-element has dropped the "on" feature, it is non-translatable. |
||
| </div> | ||
| <div class="flex-text-block"> | ||
| <svg-icon :name="getIssueIcon(issue)" :class="['text', getIssueColor(issue)]"/> | ||
| <span class="issue-title tw-font-semibold tw-break-anywhere"> | ||
|
|
@@ -69,9 +60,8 @@ async function load(issuePathInfo: IssuePathInfo) { | |
| <!-- eslint-disable-next-line vue/no-v-html --> | ||
| <div v-if="issue.labels.length" v-html="renderedLabels"/> | ||
| </div> | ||
| <div class="tw-flex tw-flex-col tw-gap-2" v-if="!loading && issue === null"> | ||
| <div class="tw-text-12">{{ i18nErrorOccurred }}</div> | ||
| <div>{{ i18nErrorMessage }}</div> | ||
| <div v-else> | ||
| {{ errorMessage }} | ||
| </div> | ||
| </div> | ||
| </template> | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import {queryElems} from '../utils/dom.ts'; | ||
| import {parseIssueHref} from '../utils.ts'; | ||
| import {createApp} from 'vue'; | ||
| import ContextPopup from '../components/ContextPopup.vue'; | ||
| import {createTippy, getAttachedTippyInstance} from '../modules/tippy.ts'; | ||
|
|
||
| export function initMarkupRefIssue(el: HTMLElement) { | ||
| queryElems(el, '.ref-issue', (el) => { | ||
| el.addEventListener('mouseenter', showMarkupRefIssuePopup); | ||
| el.addEventListener('focus', showMarkupRefIssuePopup); | ||
| }); | ||
| } | ||
|
|
||
| export function showMarkupRefIssuePopup(e: MouseEvent | FocusEvent) { | ||
| const refIssue = e.currentTarget as HTMLElement; | ||
| if (getAttachedTippyInstance(refIssue)) return; | ||
| if (refIssue.classList.contains('ref-external-issue')) return; | ||
|
|
||
| const issuePathInfo = parseIssueHref(refIssue.getAttribute('href')); | ||
| if (!issuePathInfo.ownerName) return; | ||
|
|
||
| const el = document.createElement('div'); | ||
| const tippy = createTippy(refIssue, { | ||
| theme: 'default', | ||
| content: el, | ||
| trigger: 'mouseenter focus', | ||
| placement: 'top-start', | ||
| interactive: true, | ||
| role: 'dialog', | ||
| interactiveBorder: 5, | ||
| // onHide() { return false }, // help to keep the popup and debug the layout | ||
| onShow: () => { | ||
| const view = createApp(ContextPopup, { | ||
| // backend: GetIssueInfo | ||
| loadIssueInfoUrl: `${window.config.appSubUrl}/${issuePathInfo.ownerName}/${issuePathInfo.repoName}/issues/${issuePathInfo.indexString}/info`, | ||
| }); | ||
| view.mount(el); | ||
| }, | ||
| }); | ||
| tippy.show(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -209,3 +209,7 @@ export function showTemporaryTooltip(target: Element, content: Content): void { | |
| }, | ||
| }); | ||
| } | ||
|
|
||
| export function getAttachedTippyInstance(el: Element): Instance | null { | ||
| return el._tippy ?? null; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of this, can you adjust interface Element {
_tippy: import('tippy.js').Instance | undefined;
}And then just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is very ugly. I believe in we need to encapsulate the modules, but don't let caller use fragile code.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you don't want direct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There are far more, these usages can be refactored later, not in this PR's scope.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH, I find the typescript solution better, it does not necessitate such big refactors and is equally safe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A variable starting with underscore already means it is for internal usage only, it is a widely accepted agreement across many different languages. Using a variable with underscore prefix really doesn't seem good. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a practical solution in case of tippy, I agree in a ideal world, properties should not be added to We should replace tippy (which is deprecated) with https://github.com/floating-ui/floating-ui. |
||

Uh oh!
There was an error while loading. Please reload this page.