Browse Source

Make tooltips render automatically

master
Riyyi 3 weeks ago
parent
commit
9878bbdb8c
  1. 2
      .github/workflows/nuxtjs.yml
  2. 4
      nuxt.config.ts
  3. 20
      package-lock.json
  4. 3
      package.json
  5. 22
      src/app.vue
  6. 2
      src/components/articles/TableOfContents.vue
  7. 2
      src/components/articles/TableOfContentsLink.vue
  8. 53
      src/components/content/ProsePre.vue
  9. 3
      src/plugins/bootstrap.client.ts
  10. 20
      src/stores/stateStore.ts
  11. 8
      tsconfig.json

2
.github/workflows/nuxtjs.yml

@ -40,7 +40,7 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: "20" node-version: "22"
cache: npm cache: npm
- name: Install dependencies - name: Install dependencies
# run: bun install # run: bun install

4
nuxt.config.ts

@ -16,6 +16,7 @@ export default defineNuxtConfig({
] ]
}, },
toc: { toc: {
// @ts-ignore
title: "Table of Contents", title: "Table of Contents",
depth: 4, // include h4 headings depth: 4, // include h4 headings
searchDepth: 2 searchDepth: 2
@ -51,6 +52,9 @@ export default defineNuxtConfig({
}, },
srcDir: "src/", srcDir: "src/",
ssr: false, ssr: false,
typescript: {
typeCheck: true
},
vite: { vite: {
plugins: [ plugins: [
ViteComponents({ ViteComponents({

20
package-lock.json generated

@ -30,7 +30,8 @@
"nuxt": "^3.14.1592", "nuxt": "^3.14.1592",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"unplugin-icons": "^22.1.0", "unplugin-icons": "^22.1.0",
"unplugin-vue-components": "^28.4.1" "unplugin-vue-components": "^28.4.1",
"vue-tsc": "^2.2.8"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@ -12502,6 +12503,23 @@
"vue": "^3.2.0" "vue": "^3.2.0"
} }
}, },
"node_modules/vue-tsc": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.8.tgz",
"integrity": "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "~2.4.11",
"@vue/language-core": "2.2.8"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
},
"peerDependencies": {
"typescript": ">=5.0.0"
}
},
"node_modules/web-namespaces": { "node_modules/web-namespaces": {
"version": "2.0.1", "version": "2.0.1",
"dev": true, "dev": true,

3
package.json

@ -33,7 +33,8 @@
"nuxt": "^3.14.1592", "nuxt": "^3.14.1592",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"unplugin-icons": "^22.1.0", "unplugin-icons": "^22.1.0",
"unplugin-vue-components": "^28.4.1" "unplugin-vue-components": "^28.4.1",
"vue-tsc": "^2.2.8"
}, },
"trustedDependencies": [ "trustedDependencies": [
"@parcel/watcher" "@parcel/watcher"

22
src/app.vue

@ -6,7 +6,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const bootstrap = useNuxtApp().$bootstrap; import { useRouter } from "vue-router";
import { useStateStore } from "@/stores/stateStore";
const router = useRouter();
const store = useStateStore();
useHead({ useHead({
titleTemplate: (titleChunk: string | undefined): string | null => { titleTemplate: (titleChunk: string | undefined): string | null => {
@ -14,14 +18,14 @@ useHead({
} }
}) })
// Access bootstrap after the DOM is ready // Init Bootstrap after navigation
onMounted(() => { router.afterEach((_to, _from) => {
// Initialize popovers setTimeout(() => { store.initBootstrap(); }, 500);
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]') });
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
// Initialize tooltips // Init Bootstrap on initial page load, after the DOM is ready
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); onMounted(() => {
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); // @ts-ignore
store.initBootstrap();
}); });
</script> </script>

2
src/components/articles/TableOfContents.vue

@ -6,7 +6,7 @@
<a href="#">Overview</a> <a href="#">Overview</a>
</li> </li>
</ul> </ul>
<ArticlesTableOfContentsLink :links="toc.links" :first="true" class="font-smaller mb-0" /> <ArticlesTableOfContentsLink :links="toc.links" :first="true" class="font-smaller" />
</aside> </aside>
</template> </template>

2
src/components/articles/TableOfContentsLink.vue

@ -1,5 +1,5 @@
<template> <template>
<ul> <ul class="mb-0">
<template v-for="link in links" :key="link.id"> <template v-for="link in links" :key="link.id">
<li> <li>
<a :href="'#' + link.id">{{ link.text }}</a> <a :href="'#' + link.id">{{ link.text }}</a>

53
src/components/content/ProsePre.vue

@ -1,12 +1,16 @@
<template> <template>
<div class="position-relative"> <div class="position-relative">
<div @click="copyCode" class="position-absolute" style="top: 10px; right: 10px;"> <div @click="copyCode" class="position-absolute" style="top: 10px; right: 10px;">
<template v-if="copied"> <button class="copy text-secondary" :class="copied ? 'd-none' : ''" data-bs-toggle="tooltip"
<code>Copied!</code> data-bs-placement="top" data-bs-title="Copy to clipboard">
</template>
<button class="copy text-secondary" title="Copy code">
<IFaClone /> <IFaClone />
</button> </button>
<button class="copy text-secondary" :class="!copied ? 'd-none' : ''" data-bs-toggle="tooltip"
data-bs-placement="top" data-bs-title="Copied!" data-bs-trigger="manual" data-bs-show
ref="stickyTooltipElement">
<IFaCheck class="text-success" />
</button>
</div> </div>
<pre :class="$props.class"><code class="language-{{ language }}"><slot /></code></pre> <pre :class="$props.class"><code class="language-{{ language }}"><slot /></code></pre>
</div> </div>
@ -30,7 +34,9 @@ button.copy:hover {
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue" import { nextTick, ref, watch } from "vue"
const bootstrap = useNuxtApp().$bootstrap;
defineProps({ defineProps({
code: { code: {
@ -59,19 +65,48 @@ defineProps({
} }
}); });
const copied = ref(false); const copied = ref<boolean>(false);
const copyCode = async (e: Event) => { const copyCode = async (e: Event) => {
try { try {
const target = e.currentTarget as HTMLElement; const target = e.currentTarget as HTMLElement;
const element = target.nextElementSibling as HTMLPreElement | null; const element = target.nextElementSibling as HTMLPreElement | null;
const textToCopy = element ? element.textContent.trim() : ""; const textToCopy = element ? element.textContent!.trim() : "";
await navigator.clipboard.writeText(textToCopy); await navigator.clipboard.writeText(textToCopy);
copied.value = true; copied.value = true;
setTimeout(() => (copied.value = false), 2000); setTimeout(() => { copied.value = false; }, 2000);
} catch (err) { } catch (err) {
console.error('Failed to copy:', err) console.error('Failed to copy:', err);
}
}
// Clicked tooltip
const stickyTooltip = ref<any>();
const stickyTooltipElement = ref<HTMLInputElement | null>(null);
watch(copied, (newValue, oldValue) => {
if (newValue !== oldValue) {
if (newValue) {
// Ensure the DOM is updated before executing
nextTick(() => {
// @ts-ignore
stickyTooltip.value = new bootstrap.Tooltip(stickyTooltipElement.value, { trigger: "manual" });
stickyTooltip.value.show();
});
}
else {
if (stickyTooltip.value) {
stickyTooltip.value.dispose();
stickyTooltip.value = null;
} }
} }
}
});
onBeforeUnmount(() => {
if (stickyTooltip.value) {
stickyTooltip.value.dispose();
stickyTooltip.value = null;
}
});
</script> </script>

3
src/plugins/bootstrap.client.ts

@ -1,5 +1,6 @@
// @ts-ignore
import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min"; import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min";
export default defineNuxtPlugin(nuxtApp => { export default defineNuxtPlugin(nuxtApp => {
nuxtApp.provide("bootstrap", bootstrap); nuxtApp.provide("bootstrap", bootstrap);
}) });

20
src/stores/stateStore.ts

@ -1,6 +1,9 @@
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { ref } from "vue"; import { ref } from "vue";
// @ts-ignore
import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min";
export const useStateStore = defineStore("state", () => { export const useStateStore = defineStore("state", () => {
const colorMode = ref<string>("light"); const colorMode = ref<string>("light");
@ -10,7 +13,22 @@ export const useStateStore = defineStore("state", () => {
html.setAttribute('data-bs-theme', colorMode.value); html.setAttribute('data-bs-theme', colorMode.value);
}; };
return { colorMode, toggleColorMode } const popoverList = ref<any[]>([]);
const tooltipList = ref<any[]>([]);
const initBootstrap = (): void => {
// Initialize popovers
popoverList.value.forEach(popover => popover.dispose());
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
popoverList.value = [...popoverTriggerList].map(popover => new bootstrap.Popover(popover));
// Initialize tooltips
tooltipList.value.forEach(tooltip => tooltip.dispose());
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]:not([data-bs-show])');
tooltipList.value = [...tooltipTriggerList].map(tooltip => new bootstrap.Tooltip(tooltip));
};
return { colorMode, toggleColorMode, initBootstrap }
}, { }, {
persist: process.env.NODE_ENV === 'development' ? true : false, persist: process.env.NODE_ENV === 'development' ? true : false,
}) })

8
tsconfig.json

@ -1,10 +1,4 @@
{ {
// https://nuxt.com/docs/guide/concepts/typescript // https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json", "extends": "./.nuxt/tsconfig.json"
"compilerOptions": {
"types": [
"bootstrap",
"unplugin-icons/types/vue"
]
}
} }

Loading…
Cancel
Save