ContentRenderer.svelte 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. <script>
  2. import { onDestroy, onMount, tick, createEventDispatcher } from 'svelte';
  3. const dispatch = createEventDispatcher();
  4. import Markdown from './Markdown.svelte';
  5. import LightBlub from '$lib/components/icons/LightBlub.svelte';
  6. import { mobile, showArtifacts, showControls, showOverview } from '$lib/stores';
  7. export let id;
  8. export let content;
  9. export let model = null;
  10. export let save = false;
  11. export let floatingButtons = true;
  12. let contentContainerElement;
  13. let buttonsContainerElement;
  14. const updateButtonPosition = (event) => {
  15. setTimeout(async () => {
  16. await tick();
  17. // Check if the event target is within the content container
  18. if (!contentContainerElement?.contains(event.target)) return;
  19. let selection = window.getSelection();
  20. if (selection.toString().trim().length > 0) {
  21. const range = selection.getRangeAt(0);
  22. const rect = range.getBoundingClientRect();
  23. const parentRect = contentContainerElement.getBoundingClientRect();
  24. // Adjust based on parent rect
  25. const top = rect.bottom - parentRect.top;
  26. const left = rect.left - parentRect.left;
  27. if (buttonsContainerElement) {
  28. buttonsContainerElement.style.display = 'block';
  29. buttonsContainerElement.style.left = `${left}px`;
  30. buttonsContainerElement.style.top = `${top + 5}px`; // +5 to add some spacing
  31. }
  32. } else {
  33. if (buttonsContainerElement) {
  34. buttonsContainerElement.style.display = 'none';
  35. }
  36. }
  37. }, 0);
  38. };
  39. onMount(() => {
  40. if (floatingButtons) {
  41. contentContainerElement?.addEventListener('mouseup', updateButtonPosition);
  42. document.addEventListener('mouseup', updateButtonPosition);
  43. }
  44. });
  45. onDestroy(() => {
  46. if (floatingButtons) {
  47. contentContainerElement?.removeEventListener('mouseup', updateButtonPosition);
  48. document.removeEventListener('mouseup', updateButtonPosition);
  49. }
  50. });
  51. </script>
  52. <div bind:this={contentContainerElement}>
  53. <Markdown
  54. {id}
  55. {content}
  56. {model}
  57. {save}
  58. on:update={(e) => {
  59. dispatch('update', e.detail);
  60. }}
  61. on:code={(e) => {
  62. const { lang } = e.detail;
  63. console.log('code', lang);
  64. if (['html', 'svg'].includes(lang) && !$mobile) {
  65. showArtifacts.set(true);
  66. showControls.set(true);
  67. }
  68. }}
  69. />
  70. </div>
  71. {#if floatingButtons}
  72. <div
  73. bind:this={buttonsContainerElement}
  74. class="absolute rounded-lg mt-1 p-1 bg-white dark:bg-gray-850 text-xs text-medium shadow-lg"
  75. style="display: none"
  76. >
  77. <button
  78. class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-0.5 min-w-fit"
  79. on:click={() => {
  80. const selection = window.getSelection();
  81. dispatch('explain', selection.toString());
  82. // Clear selection
  83. selection.removeAllRanges();
  84. buttonsContainerElement.style.display = 'none';
  85. }}
  86. >
  87. <LightBlub className="size-3 shrink-0" />
  88. <div class="shrink-0">Explain</div>
  89. </button>
  90. </div>
  91. {/if}