Newer
Older
navi-1 / webclient / src / components / settings / SynapseInstructionsField.vue
<template>
  <div class="reactions-instructions" :data-doc="doc">
    <div class="reactions-row-title">{{ title }}</div>
    <div class="reactions-row-status">{{ note }}</div>
    <GnTextarea
      :model-value="modelValue"
      :rows="6"
      :placeholder="placeholder"
      class="reactions-instructions-textarea"
      @update:model-value="$emit('update:modelValue', $event)"
    />
    <div class="reactions-instructions-actions">
      <GnButton
        variant="primary"
        :disabled="!dirty || saving"
        @click="$emit('save')"
      >
        {{ saveLabel }}
      </GnButton>
    </div>

    <div v-if="versions.length" class="reactions-history">
      <div class="reactions-history-title">{{ t('settings.reactionsHistory') }}</div>
      <div
        v-for="v in versions"
        :key="v.created_at + ':' + v.content.slice(0, 24)"
        class="reactions-history-item"
      >
        <span class="reactions-history-meta">
          <i
            class="ph"
            :class="v.edited_by === 'navi' ? 'ph-robot' : 'ph-user'"
          ></i>
          {{ v.edited_by === 'navi' ? 'Navi' : t('common.you') }}
          · {{ formatWhen(v.created_at) }}
          <template v-if="v.reason"> · {{ v.reason }}</template>
        </span>
        <div class="reactions-history-text">{{ v.content }}</div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { locale, t } from '@/i18n/index.js'
import { GnButton, GnTextarea } from 'gnexus-ui-kit/vue'

// One instruction document (reaction / dispatcher) rendered the same way:
// same classes, `data-doc` tells the two blocks apart in tests and style hooks.
defineProps({
  doc: { type: String, required: true },
  title: { type: String, required: true },
  note: { type: String, required: true },
  placeholder: { type: String, default: '' },
  saveLabel: { type: String, required: true },
  modelValue: { type: String, default: '' },
  versions: { type: Array, default: () => [] },
  dirty: { type: Boolean, default: false },
  saving: { type: Boolean, default: false },
})
defineEmits(['update:modelValue', 'save'])

function formatWhen(iso) {
  const d = new Date(iso)
  return d.toLocaleDateString(locale.value, { month: 'short', day: 'numeric' }) +
    ' ' + d.toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit' })
}
</script>

<style scoped lang="scss">
.reactions-instructions {
  padding: 14px 16px;
  margin-bottom: 12px;
  border: 1px solid var(--gn-border-color-muted);
  border-radius: 8px;
}

.reactions-row-title {
  font-size: 0.9rem;
  color: var(--gn-color-text-medium);
  font-weight: 500;
}

.reactions-row-status {
  font-size: 0.8rem;
  color: var(--gn-color-text-dark);
  margin-top: 2px;
  max-width: 420px;
}

.reactions-instructions-textarea {
  margin-top: 10px;
}

.reactions-instructions-actions {
  margin-top: 10px;
  display: flex;
  justify-content: flex-end;
}

.reactions-history {
  margin-top: 14px;
}

.reactions-history-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--gn-color-text-medium);
  margin-bottom: 8px;
}

.reactions-history-item {
  padding: 8px 10px;
  border-left: 2px solid var(--gn-border-color-muted);

  & + & {
    margin-top: 8px;
  }
}

.reactions-history-meta {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.75rem;
  color: var(--gn-color-text-dark);

  i {
    font-size: 12px;
  }
}

.reactions-history-text {
  margin-top: 4px;
  font-size: 0.8rem;
  color: var(--gn-color-text-medium);
  white-space: pre-wrap;
  word-break: break-word;
}
</style>