ComposeView.svelte raw

   1  <script>
   2      export let composeEventJson = "";
   3      export let userPubkey = "";
   4      export let userRole = "";
   5      export let policyEnabled = false;
   6      export let publishError = "";
   7      export let localOnly = true;
   8  
   9      import { createEventDispatcher } from "svelte";
  10      import EventTemplateSelector from "./EventTemplateSelector.svelte";
  11  
  12      const dispatch = createEventDispatcher();
  13  
  14      let isTemplateSelectorOpen = false;
  15  
  16      function reformatJson() {
  17          dispatch("reformatJson");
  18      }
  19  
  20      function signEvent() {
  21          dispatch("signEvent");
  22      }
  23  
  24      function publishEvent() {
  25          dispatch("publishEvent");
  26      }
  27  
  28      function openTemplateSelector() {
  29          isTemplateSelectorOpen = true;
  30      }
  31  
  32      function handleTemplateSelect(event) {
  33          const { kind, template } = event.detail;
  34          composeEventJson = JSON.stringify(template, null, 2);
  35          dispatch("templateSelected", { kind, template });
  36      }
  37  
  38      function handleTemplateSelectorClose() {
  39          isTemplateSelectorOpen = false;
  40      }
  41  
  42      function clearError() {
  43          publishError = "";
  44          dispatch("clearError");
  45      }
  46  </script>
  47  
  48  <div class="compose-view">
  49      <div class="compose-header">
  50          <button class="compose-btn template-btn" on:click={openTemplateSelector}
  51              >Generate Template</button
  52          >
  53          <button class="compose-btn reformat-btn" on:click={reformatJson}
  54              >Reformat</button
  55          >
  56          <button class="compose-btn sign-btn" on:click={signEvent}>Sign</button>
  57          <label class="local-only-label">
  58              <input type="checkbox" bind:checked={localOnly} />
  59              This relay only
  60          </label>
  61          <button class="compose-btn publish-btn" on:click={publishEvent}
  62              >Publish</button
  63          >
  64      </div>
  65  
  66      {#if publishError}
  67          <div class="error-banner">
  68              <div class="error-content">
  69                  <span class="error-icon">&#9888;</span>
  70                  <span class="error-message">{publishError}</span>
  71              </div>
  72              <button class="error-dismiss" on:click={clearError}>&times;</button>
  73          </div>
  74      {/if}
  75  
  76      <div class="compose-editor">
  77          <textarea
  78              bind:value={composeEventJson}
  79              class="compose-textarea"
  80              placeholder="Enter your Nostr event JSON here, or click 'Generate Template' to start with a template..."
  81              spellcheck="false"
  82          ></textarea>
  83      </div>
  84  </div>
  85  
  86  <EventTemplateSelector
  87      bind:isOpen={isTemplateSelectorOpen}
  88      {userPubkey}
  89      on:select={handleTemplateSelect}
  90      on:close={handleTemplateSelectorClose}
  91  />
  92  
  93  <style>
  94      .compose-view {
  95          position: fixed;
  96          top: 3em;
  97          left: 200px;
  98          right: 0;
  99          bottom: 0;
 100          display: flex;
 101          flex-direction: column;
 102          background: transparent;
 103      }
 104  
 105      .compose-header {
 106          display: flex;
 107          gap: 0.5em;
 108          padding: 0.5em;
 109          background: transparent;
 110      }
 111  
 112      .compose-btn {
 113          padding: 0.5em 1em;
 114          border: 1px solid var(--border-color);
 115          border-radius: 0.25rem;
 116          background: var(--button-bg);
 117          color: var(--button-text);
 118          cursor: pointer;
 119          font-size: 0.9rem;
 120          transition: background-color 0.2s;
 121      }
 122  
 123      .compose-btn:hover {
 124          background: var(--button-hover-bg);
 125      }
 126  
 127      .template-btn {
 128          background: var(--primary);
 129          color: var(--text-color);
 130      }
 131  
 132      .template-btn:hover {
 133          background: var(--primary);
 134          filter: brightness(0.9);
 135      }
 136  
 137      .reformat-btn {
 138          background: var(--info);
 139          color: var(--text-color);
 140      }
 141  
 142      .reformat-btn:hover {
 143          background: var(--info);
 144          filter: brightness(0.9);
 145      }
 146  
 147      .sign-btn {
 148          background: var(--warning);
 149          color: var(--text-color);
 150      }
 151  
 152      .sign-btn:hover {
 153          background: var(--warning);
 154          filter: brightness(0.9);
 155      }
 156  
 157      .local-only-label {
 158          display: flex;
 159          align-items: center;
 160          gap: 0.35em;
 161          font-size: 0.85rem;
 162          color: var(--text-color);
 163          cursor: pointer;
 164          user-select: none;
 165          margin-left: auto;
 166          padding: 0 0.5em;
 167          white-space: nowrap;
 168      }
 169  
 170      .local-only-label input[type="checkbox"] {
 171          cursor: pointer;
 172          accent-color: var(--accent-color);
 173      }
 174  
 175      .publish-btn {
 176          background: var(--success);
 177          color: var(--text-color);
 178      }
 179  
 180      .publish-btn:hover {
 181          background: var(--success);
 182          filter: brightness(0.9);
 183      }
 184  
 185      .error-banner {
 186          display: flex;
 187          align-items: center;
 188          justify-content: space-between;
 189          padding: 0.75em 1em;
 190          margin: 0 0.5em;
 191          background: #f8d7da;
 192          border: 1px solid #f5c6cb;
 193          border-radius: 0.25rem;
 194          color: #721c24;
 195      }
 196  
 197      :global(.dark-theme) .error-banner {
 198          background: #4a1c24;
 199          border-color: #6a2c34;
 200          color: #f8d7da;
 201      }
 202  
 203      .error-content {
 204          display: flex;
 205          align-items: center;
 206          gap: 0.5em;
 207      }
 208  
 209      .error-icon {
 210          font-size: 1.2em;
 211      }
 212  
 213      .error-message {
 214          font-size: 0.9rem;
 215          line-height: 1.4;
 216      }
 217  
 218      .error-dismiss {
 219          background: none;
 220          border: none;
 221          font-size: 1.25rem;
 222          cursor: pointer;
 223          color: inherit;
 224          padding: 0 0.25em;
 225          opacity: 0.7;
 226      }
 227  
 228      .error-dismiss:hover {
 229          opacity: 1;
 230      }
 231  
 232      .compose-editor {
 233          flex: 1;
 234          display: flex;
 235          flex-direction: column;
 236          padding: 0;
 237      }
 238  
 239      .compose-textarea {
 240          flex: 1;
 241          width: 100%;
 242          padding: 1em;
 243          border-radius: 0.5em;
 244          background: var(--input-bg);
 245          color: var(--input-text-color);
 246          font-family: monospace;
 247          font-size: 0.9em;
 248          line-height: 1.4;
 249          resize: vertical;
 250          outline: none;
 251      }
 252  
 253      .compose-textarea:focus {
 254          border-color: var(--accent-color);
 255          box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
 256      }
 257  
 258      @media (max-width: 1280px) {
 259          .compose-view {
 260              left: 60px;
 261          }
 262      }
 263  
 264      @media (max-width: 640px) {
 265          .compose-view {
 266              left: 0;
 267          }
 268  
 269          .compose-header {
 270              flex-wrap: wrap;
 271          }
 272  
 273          .compose-btn {
 274              flex: 1;
 275              min-width: calc(50% - 0.5em);
 276          }
 277      }
 278  </style>
 279