From 857abed3a92eb6b02a9fd14d2eff20b7a94ed8b4 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Wed, 18 Dec 2024 21:26:17 +0100
Subject: [PATCH] Move RepoBranchTagSelector init outside the SFC (#32890)

SFCs shouldn't export anything besides their component, and this
eliminates one issue with tsc, while apparently also solving a hack. It
seems to work as before, also when multiples are on the same page.
---
 .../js/components/RepoBranchTagSelector.vue   | 75 ++++++++-----------
 web_src/js/features/repo-legacy.ts            |  9 ++-
 web_src/js/globals.d.ts                       |  1 -
 3 files changed, 38 insertions(+), 47 deletions(-)

diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue
index 4b7ca1429d..a5ed8b6dad 100644
--- a/web_src/js/components/RepoBranchTagSelector.vue
+++ b/web_src/js/components/RepoBranchTagSelector.vue
@@ -1,5 +1,5 @@
 <script lang="ts">
-import {createApp, nextTick} from 'vue';
+import {nextTick} from 'vue';
 import {SvgIcon} from '../svg.ts';
 import {showErrorToast} from '../modules/toast.ts';
 import {GET} from '../modules/fetch.ts';
@@ -17,11 +17,11 @@ type SelectedTab = 'branches' | 'tags';
 
 type TabLoadingStates = Record<SelectedTab, '' | 'loading' | 'done'>
 
-let currentElRoot: HTMLElement;
-
 const sfc = {
   components: {SvgIcon},
-
+  props: {
+    elRoot: HTMLElement,
+  },
   computed: {
     searchFieldPlaceholder() {
       return this.selectedTab === 'branches' ? this.textFilterBranch : this.textFilterTag;
@@ -55,18 +55,15 @@ const sfc = {
       return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName)}`;
     },
   },
-
   watch: {
-    menuVisible(visible) {
+    menuVisible(visible: boolean) {
       if (!visible) return;
       this.focusSearchField();
       this.loadTabItems();
     },
   },
-
   data() {
-    const elRoot = currentElRoot;
-    const shouldShowTabBranches = elRoot.getAttribute('data-show-tab-branches') === 'true';
+    const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true';
     return {
       csrfToken: window.config.csrfToken,
       allItems: [] as ListItem[],
@@ -76,37 +73,35 @@ const sfc = {
       activeItemIndex: 0,
       tabLoadingStates: {} as TabLoadingStates,
 
-      textReleaseCompare: elRoot.getAttribute('data-text-release-compare'),
-      textBranches: elRoot.getAttribute('data-text-branches'),
-      textTags: elRoot.getAttribute('data-text-tags'),
-      textFilterBranch: elRoot.getAttribute('data-text-filter-branch'),
-      textFilterTag: elRoot.getAttribute('data-text-filter-tag'),
-      textDefaultBranchLabel: elRoot.getAttribute('data-text-default-branch-label'),
-      textCreateTag: elRoot.getAttribute('data-text-create-tag'),
-      textCreateBranch: elRoot.getAttribute('data-text-create-branch'),
-      textCreateRefFrom: elRoot.getAttribute('data-text-create-ref-from'),
-      textNoResults: elRoot.getAttribute('data-text-no-results'),
-      textViewAllBranches: elRoot.getAttribute('data-text-view-all-branches'),
-      textViewAllTags: elRoot.getAttribute('data-text-view-all-tags'),
+      textReleaseCompare: this.elRoot.getAttribute('data-text-release-compare'),
+      textBranches: this.elRoot.getAttribute('data-text-branches'),
+      textTags: this.elRoot.getAttribute('data-text-tags'),
+      textFilterBranch: this.elRoot.getAttribute('data-text-filter-branch'),
+      textFilterTag: this.elRoot.getAttribute('data-text-filter-tag'),
+      textDefaultBranchLabel: this.elRoot.getAttribute('data-text-default-branch-label'),
+      textCreateTag: this.elRoot.getAttribute('data-text-create-tag'),
+      textCreateBranch: this.elRoot.getAttribute('data-text-create-branch'),
+      textCreateRefFrom: this.elRoot.getAttribute('data-text-create-ref-from'),
+      textNoResults: this.elRoot.getAttribute('data-text-no-results'),
+      textViewAllBranches: this.elRoot.getAttribute('data-text-view-all-branches'),
+      textViewAllTags: this.elRoot.getAttribute('data-text-view-all-tags'),
 
-      currentRepoDefaultBranch: elRoot.getAttribute('data-current-repo-default-branch'),
-      currentRepoLink: elRoot.getAttribute('data-current-repo-link'),
-      currentTreePath: elRoot.getAttribute('data-current-tree-path'),
-      currentRefType: elRoot.getAttribute('data-current-ref-type'),
-      currentRefShortName: elRoot.getAttribute('data-current-ref-short-name'),
+      currentRepoDefaultBranch: this.elRoot.getAttribute('data-current-repo-default-branch'),
+      currentRepoLink: this.elRoot.getAttribute('data-current-repo-link'),
+      currentTreePath: this.elRoot.getAttribute('data-current-tree-path'),
+      currentRefType: this.elRoot.getAttribute('data-current-ref-type'),
+      currentRefShortName: this.elRoot.getAttribute('data-current-ref-short-name'),
 
-      refLinkTemplate: elRoot.getAttribute('data-ref-link-template'),
-      refFormActionTemplate: elRoot.getAttribute('data-ref-form-action-template'),
-      dropdownFixedText: elRoot.getAttribute('data-dropdown-fixed-text'),
+      refLinkTemplate: this.elRoot.getAttribute('data-ref-link-template'),
+      refFormActionTemplate: this.elRoot.getAttribute('data-ref-form-action-template'),
+      dropdownFixedText: this.elRoot.getAttribute('data-dropdown-fixed-text'),
       showTabBranches: shouldShowTabBranches,
-      showTabTags: elRoot.getAttribute('data-show-tab-tags') === 'true',
-      allowCreateNewRef: elRoot.getAttribute('data-allow-create-new-ref') === 'true',
-      showViewAllRefsEntry: elRoot.getAttribute('data-show-view-all-refs-entry') === 'true',
-
-      enableFeed: elRoot.getAttribute('data-enable-feed') === 'true',
+      showTabTags: this.elRoot.getAttribute('data-show-tab-tags') === 'true',
+      allowCreateNewRef: this.elRoot.getAttribute('data-allow-create-new-ref') === 'true',
+      showViewAllRefsEntry: this.elRoot.getAttribute('data-show-view-all-refs-entry') === 'true',
+      enableFeed: this.elRoot.getAttribute('data-enable-feed') === 'true',
     };
   },
-
   beforeMount() {
     document.body.addEventListener('click', (e) => {
       if (this.$el.contains(e.target)) return;
@@ -219,16 +214,6 @@ const sfc = {
   },
 };
 
-export function initRepoBranchTagSelector(selector) {
-  for (const elRoot of document.querySelectorAll(selector)) {
-    // it is very hacky, but it is the only way to pass the elRoot to the "data()" function
-    // it could be improved in the future to do more rewriting.
-    currentElRoot = elRoot;
-    const comp = {...sfc};
-    createApp(comp).mount(elRoot);
-  }
-}
-
 export default sfc; // activate IDE's Vue plugin
 </script>
 <template>
diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts
index eaa9e3742a..04267d1dda 100644
--- a/web_src/js/features/repo-legacy.ts
+++ b/web_src/js/features/repo-legacy.ts
@@ -7,7 +7,6 @@ import {
   initRepoPullRequestUpdate,
 } from './repo-issue.ts';
 import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
-import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
 import {initRepoCloneButtons} from './repo-common.ts';
 import {initCitationFileCopyContent} from './citation.ts';
 import {initCompLabelEdit} from './comp/LabelEdit.ts';
@@ -20,6 +19,14 @@ import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts';
 import {initRepoIssueCommentEdit} from './repo-issue-edit.ts';
 import {initRepoMilestone} from './repo-milestone.ts';
 import {initRepoNew} from './repo-new.ts';
+import {createApp} from 'vue';
+import RepoBranchTagSelector from '../components/RepoBranchTagSelector.vue';
+
+function initRepoBranchTagSelector(selector: string) {
+  for (const elRoot of document.querySelectorAll(selector)) {
+    createApp(RepoBranchTagSelector, {elRoot}).mount(elRoot);
+  }
+}
 
 export function initBranchSelectorTabs() {
   const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts
index a5ec29a83f..c08ff9976b 100644
--- a/web_src/js/globals.d.ts
+++ b/web_src/js/globals.d.ts
@@ -14,7 +14,6 @@ declare module '*.vue' {
   export default component;
   // List of named exports from vue components, used to make `tsc` output clean.
   // To actually lint .vue files, `vue-tsc` is used because `tsc` can not parse them.
-  export function initRepoBranchTagSelector(selector: string): void;
   export function initDashboardRepoList(): void;
   export function initRepositoryActionView(): void;
 }