ContentRenderer.svelte 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  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 { 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. if (lang === 'html') {
  64. showArtifacts.set(true);
  65. showControls.set(true);
  66. }
  67. }}
  68. />
  69. </div>
  70. {#if floatingButtons}
  71. <div
  72. bind:this={buttonsContainerElement}
  73. class="absolute rounded-lg mt-1 p-1 bg-white dark:bg-gray-850 text-xs text-medium shadow-lg"
  74. style="display: none"
  75. >
  76. <button
  77. class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-0.5 min-w-fit"
  78. on:click={() => {
  79. const selection = window.getSelection();
  80. dispatch('explain', selection.toString());
  81. // Clear selection
  82. selection.removeAllRanges();
  83. buttonsContainerElement.style.display = 'none';
  84. }}
  85. >
  86. <LightBlub className="size-3 shrink-0" />
  87. <div class="shrink-0">Explain</div>
  88. </button>
  89. </div>
  90. {/if}