open to eln

open to eln

<template>

              <input type="checkbox" v-model="iss_search"/>

          <input type="text" class="form-control" v-model="pk" @keyup.enter="load" autofocus
                 :placeholder="iss_search ? 'Номер м/б анализа' : 'Номер направления'"/>

            <button class="btn last btn-blue-nb nbr" type="button" @click="load" style="margin-right: -1px">
              Поиск
            </button>

        Результаты за
        <date-field-nav :brn="false" :def="date" :val.sync="date" w="100px"/>

              {{direction.patient}}, {{direction.card}}

                  {{i.title}}

                  <iss-status :i="i" short/>

            <template v-if="direction.amd !== 'not_need'">

АМД: не отправлено

АМД: отправлено
                ({{direction.amd_number}})

АМД: ошибка

АМД: запланировано

            </template>

Просмотр

                <a :href="`/forms/pdf?type=105.02&napr_id=[${direction.pk}]`"
                   target="_blank" v-if="direction.all_confirmed && stat_btn">Статталон

                Печать

            Нет данных

            Очередь за <input :readonly="location.loading"
                              class="inline-form"
                              required
                              type="date" v-model="td"/>

              <colgroup>
                <col width="38"/>
                <col/>
                <col width="16"/>
              </colgroup>

 0 ? open_fill_slot(r.status.direction) : open_slot(r)"
                  v-tippy="{ placement : 'top', arrow: true, animation: 'fade' }"
                  :title="{
                  1: 'Направление зарегистрировано',
                  2: 'Результат подтверждён'}[r.status.code] || 'Не обработано'">
                {{r.timeStart}}
                {{r.patient}}

                  <span class="slot"
                        :class="`slot-${r.status.code}`">

                  загрузка...

                нет данных на дату

 0 && (stat_btn || amd)"
             class="side-bottom"
             :class="{
                'side-bottom_all': stat_btn && amd,
                'side-bottom_stat': stat_btn && !amd,
                'side-bottom_amd': !stat_btn && amd
             }"
        >
          <a v-if="stat_btn" class="btn btn-blue-nb"
             :href="`/forms/preview?type=105.01&date=${date_to_form}`" target="_blank">печать статталонов
          отправить в амд

        {{sidebarIsOpened ? 'закрыть поиск и результаты' : 'открыть поиск и результаты'}}

Загруженное направление:

            №{{data.direction.pk}} от {{data.direction.date}}

{{data.patient.fio_age}}

              Услуга: {{row.research.title}}

направление не загружено

              Направление
              №{{data.direction.pk}}
              от
              {{data.direction.date}}

{{data.patient.fio_age}}

Диагноз:
              {{data.direction.diagnos}}

Источник финансирования: {{data.direction.fin_source}}

Карта: {{data.patient.card}}

                Анамнез жизни

                загрузка...

{{anamnesis_data.text || 'нет данных'}}

                Диспансерный учёт

                загрузка...

                    {{r.diagnos}} – {{r.date_start}} – {{r.illnes}}

нет активных записей

                Льготы пациента

                загрузка...

                    {{r.benefit}} – {{r.date_start}} – {{r.registration_basis}}

нет активных записей

              <a style="margin-left: 3px"
                 href="#"
                 :class="{[`disp_${data.status_disp}`]: true}"
                 v-if="data.card_internal && data.has_doc_referral"
                 @click.prevent
                 v-tippy="{ placement : 'bottom', arrow: true, reactive : true,
                    theme : 'light bordered',
                    html: '#template-disp',
                    interactive : true }">Д

                Диспансеризация

                        требуется

                          пройдено

                      {{d[5]}}

                   !d[2]).map(d => d[0]))">
                    Выбрать требуемые

                   d[2]))">
                    Печать всех результатов

              <medical-certificates :med_certificates="data.medical_certificates" :direction="data.direction.pk"/>
              <ResultsByYear :card_pk="data.patient.card_pk" isDocReferral/>
              <ResultsByYear :card_pk="data.patient.card_pk" isParaclinic/>
              <ResultsByYear :card_pk="data.patient.card_pk" isLab/>

                ЭЛН

Лечащий врач:
              {{data.patient.doc}}

Организация: {{data.patient.imported_org}}

            <button type="button" class="close" @click="clear()">
              ×
            </button>

              {{row.research.title}}
               [{{row.research.comment}}]
              <dropdown :visible="research_open_history === row.pk"
                        :position='["left", "bottom", "left", "top"]'
                        v-if="!data.has_microbiology"
                        @clickout="hide_results">

                  (другие результаты)

                      Результат от {{r.date}}
                      печать
                      скопировать

результатов не найдено

              </dropdown>

              <template v-if="data.direction.all_confirmed">
                <a :href="`/forms/pdf?type=105.02&napr_id=[${data.direction.pk}]`"
                   class="btn btn-blue-nb" target="_blank" v-if="stat_btn">Статталон
                Печать
              </template>
              <template v-if="!data.has_microbiology">

                <template v-if="!row.confirmed">
                  <button class="btn btn-blue-nb" @click="save(row)" v-if="!row.confirmed"
                          title="Сохранить без подтверждения" v-tippy>

                  </button>
                  <button class="btn btn-blue-nb" @click="clear_vals(row)" title="Очистить протокол" v-tippy>

                  </button>

                    <select-picker-m v-model="templates[row.pk]"
                                     :search="true"
                                     :options="row.templates.map(x => ({label: x.title, value: x.pk}))"/>

                  <button class="btn btn-blue-nb" @click="load_template(row, templates[row.pk])" v-if="fte">
                    Загрузить шаблон
                  </button>
                </template>
              </template>

          <DescriptiveForm
            :research="row.research"
            :confirmed="row.confirmed"
            :patient="data.patient"
            :change_mkb="change_mkb(row)"
          />

 0)">

Дополнительные услуги

                <researches-picker v-model="row.more" :hidetemplates="true"
                                   :readonly="row.confirmed"
                                   :just_search="true"
                                   :filter_types="[2, 7]"/>

                <selected-researches :researches="row.more"
                                     :readonly="row.confirmed" :simple="true"/>

          <template v-if="data.has_microbiology">

                    Номер анализа

                    {{row.tube.pk}}

                    Ёмкость

                    {{row.tube.type}}, дата забора {{row.tube.get}}
                    печать ш/к

            <BacMicroForm
              :confirmed="row.confirmed"
              v-model="row.microbiology.bacteries"
              :cultureCommentsTemplates="row.microbiology.cultureCommentsTemplates"
            />

Заключение

 0),
                  mouseleave: leave_field(row.microbiology.conclusionTemplates.length > 0),
                 }" class="field">
                    <FastTemplates
                      :update_value="updateValue(row.microbiology, 'conclusion')"
                      :value="row.microbiology.conclusion || ''"
                      :values="row.microbiology.conclusionTemplates"
                      :confirmed="row.confirmed"
                    />

                      <textarea rows="5" class="form-control"
                                :readonly="row.confirmed" v-model="row.microbiology.conclusion"
                      />

          </template>

Рецепты

                  <recipe-input v-model="row.recipe" :pk="row.pk" :confirmed="row.confirmed"/>

Направления в рамках приёма

                  <directions-history :iss_pk="row.pk" kk="cd"/>

                  <button @click="create_directions(row)"
                          class="btn btn-primary-nb btn-blue-nb" type="button">
                     создать направления
                  </button>

Данные статталона

                  Цель посещения

                  <select v-model="row.purpose" :disabled="row.confirmed">
                    <option v-for="o in row.purpose_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Впервые

                  <input type="checkbox" id="first-time" v-model="row.first_time" :disabled="row.confirmed"/>

                  Результат обращения

                  <select v-model="row.result" :disabled="row.confirmed">
                    <option v-for="o in row.result_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Исход

                  <select v-model="row.outcome" :disabled="row.confirmed">
                    <option v-for="o in row.outcome_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Заключительный диагноз

                  <m-k-b-field v-model="row.diagnos"/>

                  <input v-model="row.diagnos" class="form-control" :readonly="true"/>

                  Дата осмотра

                  <input :max="tdm()" :min="td_m_year" :readonly="row.confirmed" class="form-control"
                         required style="width: 160px" type="date" v-model="row.examination_date"/>

                  Место оказания

                  <select v-model="row.place" :disabled="row.confirmed">
                    <option v-for="o in row.place_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Источник финансирования

                  <select v-model="row.fin_source" :disabled="row.confirmed">
                    <option v-for="o in row.fin_source_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Подозрение на онко

                  <input type="checkbox" id="onco" v-model="row.maybe_onco" :disabled="row.confirmed"/>

{{row.research.title}}:

            <iss-status :i="row"/>
            <button class="btn btn-blue-nb" @click="save(row)" v-if="!row.confirmed">Сохранить</button>
            <button class="btn btn-blue-nb" @click="save_and_confirm(row)" v-if="!row.confirmed && can_confirm"
                    :disabled="!r(row)">
              Сохранить и подтвердить
            </button>
            <button class="btn btn-blue-nb" @click="reset_confirm(row)"
                    v-if="row.confirmed && row.allow_reset_confirm && can_confirm">
              Сброс подтверждения
            </button>
            <template v-if="amd">

АМД: запланировано

АМД: ошибка

АМД: не отправлено

АМД: отправлено
                ({{data.direction.amd_number}})

              <button class="btn btn-blue-nb" @click="reset_amd([data.direction.pk])"
                      v-if="can_reset_amd && data.direction.amd !== 'not_need' && data.direction.amd !== 'need'">
                Сброс статуса АМД
              </button>
              <button class="btn btn-blue-nb" @click="send_to_amd([data.direction.pk])"
                      v-if="data.direction.amd === 'need' || data.direction.amd === 'error'">
                Отправить в АМД
              </button>
            </template>

Не заполнено:

{{rl}};

    <modal v-if="anamnesis_edit" ref="modalAnamnesisEdit" @close="hide_modal_anamnesis_edit" show-footer="true"
           white-bg="true" max-width="710px" width="100%" marginLeftRight="auto" margin-top>
      Редактор анамнеза жизни – карта {{data.patient.card}}, {{data.patient.fio_age}}

          <textarea v-model="anamnesis_data.text" rows="14" class="form-control"
                    placeholder="Анамнез жизни"></textarea>

            <button @click="hide_modal_anamnesis_edit" class="btn btn-primary-nb btn-blue-nb" type="button">
              Отмена
            </button>

            <button @click="save_anamnesis()" class="btn btn-primary-nb btn-blue-nb" type="button">
              Сохранить
            </button>

    </modal>
    <modal @close="hide_modal_create_directions" margin-top marginLeftRight="auto"
           max-width="1400px" ref="modalCD" show-footer="true" v-if="create_directions_for > -1" white-bg="true"
           width="100%">
      Создание направлений – карта {{data.patient.card}}, {{data.patient.fio_age}}

            <researches-picker v-model="create_directions_data"
                               kk="cd" style="border-top: 1px solid #eaeaea;border-bottom: 1px solid #eaeaea;" :filter_types="[7]" />

            <selected-researches
              kk="cd"
              :base="bases_obj[data.patient.base]"
              :researches="create_directions_data"
              :main_diagnosis="create_directions_diagnosis"
              :valid="true"
              :card_pk="data.patient.card_pk"
              :initial_fin="data.direction.fin_source_id"
              :parent_iss="create_directions_for"
              :clear_after_gen="true"
              style="border-top: 1px solid #eaeaea;border-bottom: 1px solid #eaeaea;"
            />

 0"
             style="margin-top: 5px;text-align: left">

            <colgroup>
              <col width="180">
              <col>
              <col width="110">
              <col width="110">
            </colgroup>

            <last-result :individual="data.patient.individual_pk" :key="p" v-for="p in create_directions_data"
                         :noScroll="true"
                         :research="p"/>

            <button @click="hide_modal_create_directions" class="btn btn-primary-nb btn-blue-nb" type="button">
              Закрыть
            </button>

    </modal>
    <modal v-if="!!slot.id" ref="modalSlot" @close="close_slot"
           margin-top="50px"
           show-footer="true" white-bg="true" max-width="710px" width="100%" marginLeftRight="auto">
      Слот {{slot.id}}

загрузка...

Талон № {{slot.data.pk}}

{{slot.data.datetime}}

          РМИС UID пациента: <a :href="`/mainmenu/directions?rmis_uid=${slot.data.patient_uid}`"
                                target="_blank">{{slot.data.patient_uid}}

Нет связанного назначения. Выберите ниже:

Выбранное назначение для талона:

            <research-pick :class="{ active: row.pk === slot.data.direction_service }" :research="row"
                           @click.native="select_research(row.pk)"
                           class="research-select"
                           v-for="row in user_services"
                           :key="row.pk"
                           v-if="!slot.data.direction || row.pk === slot.data.direction_service"/>

нет данных

            <button @click="fill_slot"
                    :disabled="slot.data.direction_service === -1"
                    v-if="!slot.data.direction"
                    class="btn btn-primary-nb btn-blue-nb" type="button">
              Сохранить назначение и заполнить протокол
            </button>
            <button @click="open_fill_slot(slot.data.direction)" v-else class="btn btn-primary-nb btn-blue-nb"
                    type="button">
              Перейти к протоколу
            </button>

            <button @click="close_slot" class="btn btn-primary-nb btn-blue-nb" type="button">
              Закрыть
            </button>

    </modal>
    <d-reg :card_pk="data.patient.card_pk" :card_data="data.patient" v-if="dreg"/>
    <benefit :card_pk="data.patient.card_pk" :card_data="data.patient" v-if="benefit" :readonly="true"/>
    <results-viewer :pk="show_results_pk" v-if="show_results_pk > -1"/>

</template>

<template>

              <input type="checkbox" v-model="iss_search"/>

          <input type="text" class="form-control" v-model="pk" @keyup.enter="load" autofocus
                 :placeholder="iss_search ? 'Номер м/б анализа' : 'Номер направления'"/>

            <button class="btn last btn-blue-nb nbr" type="button" @click="load" style="margin-right: -1px">
              Поиск
            </button>

        Результаты за
        <date-field-nav :brn="false" :def="date" :val.sync="date" w="100px"/>

              {{direction.patient}}, {{direction.card}}

                  {{i.title}}

                  <iss-status :i="i" short/>

            <template v-if="direction.amd !== 'not_need'">

АМД: не отправлено

АМД: отправлено
                ({{direction.amd_number}})

АМД: ошибка

АМД: запланировано

            </template>

Просмотр

                <a :href="`/forms/pdf?type=105.02&napr_id=[${direction.pk}]`"
                   target="_blank" v-if="direction.all_confirmed && stat_btn">Статталон

                Печать

            Нет данных

            Очередь за <input :readonly="location.loading"
                              class="inline-form"
                              required
                              type="date" v-model="td"/>

              <colgroup>
                <col width="38"/>
                <col/>
                <col width="16"/>
              </colgroup>

 0 ? open_fill_slot(r.status.direction) : open_slot(r)"
                  v-tippy="{ placement : 'top', arrow: true, animation: 'fade' }"
                  :title="{
                  1: 'Направление зарегистрировано',
                  2: 'Результат подтверждён'}[r.status.code] || 'Не обработано'">
                {{r.timeStart}}
                {{r.patient}}

                  <span class="slot"
                        :class="`slot-${r.status.code}`">

                  загрузка...

                нет данных на дату

 0 && (stat_btn || amd)"
             class="side-bottom"
             :class="{
                'side-bottom_all': stat_btn && amd,
                'side-bottom_stat': stat_btn && !amd,
                'side-bottom_amd': !stat_btn && amd
             }"
        >
          <a v-if="stat_btn" class="btn btn-blue-nb"
             :href="`/forms/preview?type=105.01&date=${date_to_form}`" target="_blank">печать статталонов
          отправить в амд

        {{sidebarIsOpened ? 'закрыть поиск и результаты' : 'открыть поиск и результаты'}}

Загруженное направление:

            №{{data.direction.pk}} от {{data.direction.date}}

{{data.patient.fio_age}}

              Услуга: {{row.research.title}}

направление не загружено

              Направление
              №{{data.direction.pk}}
              от
              {{data.direction.date}}

{{data.patient.fio_age}}

Диагноз:
              {{data.direction.diagnos}}

Источник финансирования: {{data.direction.fin_source}}

Карта: {{data.patient.card}}

                Анамнез жизни

                загрузка...

{{anamnesis_data.text || 'нет данных'}}

                Диспансерный учёт

                загрузка...

                    {{r.diagnos}} – {{r.date_start}} – {{r.illnes}}

нет активных записей

                Льготы пациента

                загрузка...

                    {{r.benefit}} – {{r.date_start}} – {{r.registration_basis}}

нет активных записей

              <a style="margin-left: 3px"
                 href="#"
                 :class="{[`disp_${data.status_disp}`]: true}"
                 v-if="data.card_internal && data.has_doc_referral"
                 @click.prevent
                 v-tippy="{ placement : 'bottom', arrow: true, reactive : true,
                    theme : 'light bordered',
                    html: '#template-disp',
                    interactive : true }">Д

                Диспансеризация

                        требуется

                          пройдено

                      {{d[5]}}

                   !d[2]).map(d => d[0]))">
                    Выбрать требуемые

                   d[2]))">
                    Печать всех результатов

              <medical-certificates :med_certificates="data.medical_certificates" :direction="data.direction.pk"/>
              <ResultsByYear :card_pk="data.patient.card_pk" isDocReferral/>
              <ResultsByYear :card_pk="data.patient.card_pk" isParaclinic/>
              <ResultsByYear :card_pk="data.patient.card_pk" isLab/>

Лечащий врач:
              {{data.patient.doc}}

Организация: {{data.patient.imported_org}}

            <button type="button" class="close" @click="clear()">
              ×
            </button>

              {{row.research.title}}
               [{{row.research.comment}}]
              <dropdown :visible="research_open_history === row.pk"
                        :position='["left", "bottom", "left", "top"]'
                        v-if="!data.has_microbiology"
                        @clickout="hide_results">

                  (другие результаты)

                      Результат от {{r.date}}
                      печать
                      скопировать

результатов не найдено

              </dropdown>

              <template v-if="data.direction.all_confirmed">
                <a :href="`/forms/pdf?type=105.02&napr_id=[${data.direction.pk}]`"
                   class="btn btn-blue-nb" target="_blank" v-if="stat_btn">Статталон
                Печать
              </template>
              <template v-if="!data.has_microbiology">

                <template v-if="!row.confirmed">
                  <button class="btn btn-blue-nb" @click="save(row)" v-if="!row.confirmed"
                          title="Сохранить без подтверждения" v-tippy>

                  </button>
                  <button class="btn btn-blue-nb" @click="clear_vals(row)" title="Очистить протокол" v-tippy>

                  </button>

                    <select-picker-m v-model="templates[row.pk]"
                                     :search="true"
                                     :options="row.templates.map(x => ({label: x.title, value: x.pk}))"/>

                  <button class="btn btn-blue-nb" @click="load_template(row, templates[row.pk])" v-if="fte">
                    Загрузить шаблон
                  </button>
                </template>
              </template>

          <DescriptiveForm
            :research="row.research"
            :confirmed="row.confirmed"
            :patient="data.patient"
            :change_mkb="change_mkb(row)"
          />

 0)">

Дополнительные услуги

                <researches-picker v-model="row.more" :hidetemplates="true"
                                   :readonly="row.confirmed"
                                   :just_search="true"
                                   :filter_types="[2, 7]"/>

                <selected-researches :researches="row.more"
                                     :readonly="row.confirmed" :simple="true"/>

          <template v-if="data.has_microbiology">

                    Номер анализа

                    {{row.tube.pk}}

                    Ёмкость

                    {{row.tube.type}}, дата забора {{row.tube.get}}
                    печать ш/к

            <BacMicroForm
              :confirmed="row.confirmed"
              v-model="row.microbiology.bacteries"
              :cultureCommentsTemplates="row.microbiology.cultureCommentsTemplates"
            />

Заключение

 0),
                  mouseleave: leave_field(row.microbiology.conclusionTemplates.length > 0),
                 }" class="field">
                    <FastTemplates
                      :update_value="updateValue(row.microbiology, 'conclusion')"
                      :value="row.microbiology.conclusion || ''"
                      :values="row.microbiology.conclusionTemplates"
                      :confirmed="row.confirmed"
                    />

                      <textarea rows="5" class="form-control"
                                :readonly="row.confirmed" v-model="row.microbiology.conclusion"
                      />

          </template>

Рецепты

                  <recipe-input v-model="row.recipe" :pk="row.pk" :confirmed="row.confirmed"/>

Направления в рамках приёма

                  <directions-history :iss_pk="row.pk" kk="cd"/>

                  <button @click="create_directions(row)"
                          class="btn btn-primary-nb btn-blue-nb" type="button">
                     создать направления
                  </button>

Данные статталона

                  Цель посещения

                  <select v-model="row.purpose" :disabled="row.confirmed">
                    <option v-for="o in row.purpose_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Впервые

                  <input type="checkbox" id="first-time" v-model="row.first_time" :disabled="row.confirmed"/>

                  Результат обращения

                  <select v-model="row.result" :disabled="row.confirmed">
                    <option v-for="o in row.result_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Исход

                  <select v-model="row.outcome" :disabled="row.confirmed">
                    <option v-for="o in row.outcome_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Заключительный диагноз

                  <m-k-b-field v-model="row.diagnos"/>

                  <input v-model="row.diagnos" class="form-control" :readonly="true"/>

                  Дата осмотра

                  <input :max="tdm()" :min="td_m_year" :readonly="row.confirmed" class="form-control"
                         required style="width: 160px" type="date" v-model="row.examination_date"/>

                  Место оказания

                  <select v-model="row.place" :disabled="row.confirmed">
                    <option v-for="o in row.place_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Источник финансирования

                  <select v-model="row.fin_source" :disabled="row.confirmed">
                    <option v-for="o in row.fin_source_list" :value="o.pk">
                      {{o.title}}
                    </option>
                  </select>

                  Подозрение на онко

                  <input type="checkbox" id="onco" v-model="row.maybe_onco" :disabled="row.confirmed"/>

{{row.research.title}}:

            <iss-status :i="row"/>
            <button class="btn btn-blue-nb" @click="save(row)" v-if="!row.confirmed">Сохранить</button>
            <button class="btn btn-blue-nb" @click="save_and_confirm(row)" v-if="!row.confirmed && can_confirm"
                    :disabled="!r(row)">
              Сохранить и подтвердить
            </button>
            <button class="btn btn-blue-nb" @click="reset_confirm(row)"
                    v-if="row.confirmed && row.allow_reset_confirm && can_confirm">
              Сброс подтверждения
            </button>
            <template v-if="amd">

АМД: запланировано

АМД: ошибка

АМД: не отправлено

АМД: отправлено
                ({{data.direction.amd_number}})

              <button class="btn btn-blue-nb" @click="reset_amd([data.direction.pk])"
                      v-if="can_reset_amd && data.direction.amd !== 'not_need' && data.direction.amd !== 'need'">
                Сброс статуса АМД
              </button>
              <button class="btn btn-blue-nb" @click="send_to_amd([data.direction.pk])"
                      v-if="data.direction.amd === 'need' || data.direction.amd === 'error'">
                Отправить в АМД
              </button>
            </template>

Не заполнено:

{{rl}};

    <modal v-if="anamnesis_edit" ref="modalAnamnesisEdit" @close="hide_modal_anamnesis_edit" show-footer="true"
           white-bg="true" max-width="710px" width="100%" marginLeftRight="auto" margin-top>
      Редактор анамнеза жизни – карта {{data.patient.card}}, {{data.patient.fio_age}}

          <textarea v-model="anamnesis_data.text" rows="14" class="form-control"
                    placeholder="Анамнез жизни"></textarea>

            <button @click="hide_modal_anamnesis_edit" class="btn btn-primary-nb btn-blue-nb" type="button">
              Отмена
            </button>

            <button @click="save_anamnesis()" class="btn btn-primary-nb btn-blue-nb" type="button">
              Сохранить
            </button>

    </modal>
    <modal @close="hide_modal_create_directions" margin-top marginLeftRight="auto"
           max-width="1400px" ref="modalCD" show-footer="true" v-if="create_directions_for > -1" white-bg="true"
           width="100%">
      Создание направлений – карта {{data.patient.card}}, {{data.patient.fio_age}}

            <researches-picker v-model="create_directions_data"
                               kk="cd" style="border-top: 1px solid #eaeaea;border-bottom: 1px solid #eaeaea;" :filter_types="[7]" />

            <selected-researches
              kk="cd"
              :base="bases_obj[data.patient.base]"
              :researches="create_directions_data"
              :main_diagnosis="create_directions_diagnosis"
              :valid="true"
              :card_pk="data.patient.card_pk"
              :initial_fin="data.direction.fin_source_id"
              :parent_iss="create_directions_for"
              :clear_after_gen="true"
              style="border-top: 1px solid #eaeaea;border-bottom: 1px solid #eaeaea;"
            />

 0"
             style="margin-top: 5px;text-align: left">

            <colgroup>
              <col width="180">
              <col>
              <col width="110">
              <col width="110">
            </colgroup>

            <last-result :individual="data.patient.individual_pk" :key="p" v-for="p in create_directions_data"
                         :noScroll="true"
                         :research="p"/>

            <button @click="hide_modal_create_directions" class="btn btn-primary-nb btn-blue-nb" type="button">
              Закрыть
            </button>

    </modal>
    <modal v-if="!!slot.id" ref="modalSlot" @close="close_slot"
           margin-top="50px"
           show-footer="true" white-bg="true" max-width="710px" width="100%" marginLeftRight="auto">
      Слот {{slot.id}}

загрузка...

Талон № {{slot.data.pk}}

{{slot.data.datetime}}

          РМИС UID пациента: <a :href="`/mainmenu/directions?rmis_uid=${slot.data.patient_uid}`"
                                target="_blank">{{slot.data.patient_uid}}

Нет связанного назначения. Выберите ниже:

Выбранное назначение для талона:

            <research-pick :class="{ active: row.pk === slot.data.direction_service }" :research="row"
                           @click.native="select_research(row.pk)"
                           class="research-select"
                           v-for="row in user_services"
                           :key="row.pk"
                           v-if="!slot.data.direction || row.pk === slot.data.direction_service"/>

нет данных

            <button @click="fill_slot"
                    :disabled="slot.data.direction_service === -1"
                    v-if="!slot.data.direction"
                    class="btn btn-primary-nb btn-blue-nb" type="button">
              Сохранить назначение и заполнить протокол
            </button>
            <button @click="open_fill_slot(slot.data.direction)" v-else class="btn btn-primary-nb btn-blue-nb"
                    type="button">
              Перейти к протоколу
            </button>

            <button @click="close_slot" class="btn btn-primary-nb btn-blue-nb" type="button">
              Закрыть
            </button>

    </modal>
    <d-reg :card_pk="data.patient.card_pk" :card_data="data.patient" v-if="dreg"/>
    <benefit :card_pk="data.patient.card_pk" :card_data="data.patient" v-if="benefit" :readonly="true"/>
    <results-viewer :pk="show_results_pk" v-if="show_results_pk > -1"/>

</template>


reload health every 30s instead of 3s

reload health every 30s instead of 3s

<template>

    <v-menu v-model="menuOpen" offset-y>
      <template v-slot:activator="{ on, attrs }">
        <v-btn v-if="activeInstance" id="instance_selection_toggle" v-bind="attrs"
               :title="`Connected to '${activeInstance.name}' (${activeInstance.uri})`" class="text-none" v-on="on">
          <svg :title="`Cluster health: ${activeInstance.status}`" height="14" width="14" class="mr-2">
            <circle :class="`health--${activeInstance.status}`" cx="7" cy="9" r="5"/>
          </svg>
          {{ activeInstance.name }}
          <v-icon v-if="menuOpen">mdi-menu-up</v-icon>
          <v-icon v-else>mdi-menu-down</v-icon>
        </v-btn>
        <v-btn v-else v-bind="attrs" class="text-none" v-on="on">
          Chose instance
          <v-icon v-if="menuOpen">mdi-menu-up</v-icon>
          <v-icon v-else>mdi-menu-down</v-icon>
        </v-btn>
      </template>

      <v-list id="instance_selection">
        <new-elasticsearch-instance :modal-open="modalOpen"/>
        <v-divider class="mt-2"/>

        <v-list-item v-for="(item, index) in instances" :key="`instance-${index}`"
                     :class="index === activeInstanceIdx ? 'v-list-item--active' : ''"
                     @click="setActiveInstanceIdx(index)">
          <v-list-item-action :title="`Cluster health: ${item.status}`">
            <svg height="14" width="14" class="mb-1 mx-auto">
              <circle :class="`health--${item.status}`" cx="7" cy="9" r="5"/>
            </svg>
          </v-list-item-action>
          <v-list-item-content :title="`Switch to cluster at '${item.uri}'`">
            <v-list-item-title class="text-truncate" style="max-width: 400px;">
              {{ item.name }} ({{ item.uri }})
            </v-list-item-title>
          </v-list-item-content>
          <v-list-item-action :id="`remove-instance-${index}`" @click.stop="removeInstance(index)">
            <v-btn icon small>
              <v-icon small>mdi-delete</v-icon>
            </v-btn>
          </v-list-item-action>
        </v-list-item>
      </v-list>
    </v-menu>

</template>


:lipstick: Clickable styling

:lipstick: Clickable styling

<template lang="pug">
button.compo.clickable
  slot
</template>

<script>
export default {
  name: 'Clickable',
};
</script>

<style lang="stylus" scoped>
.compo
  color #0366d6
  display inline
  border 0
  border-radius initial
  cursor pointer
  padding 0
  font-family inherit
  font-size inherit
  text-align left
  outline none
  background-color transparent
  &:hover
    text-decoration underline
</style>

<template lang="pug">
button.compo.clickable
  slot
</template>

<script>
export default {
  name: 'Clickable',
};
</script>

<style lang="stylus" scoped>
.compo
  color #0366d6
  display inline
  border 0
  border-radius initial
  cursor pointer
  padding 0
  font-family inherit
  font-size inherit
  text-align left
  outline none
  &:hover
    text-decoration underline
</style>


status bar - handle empty board case

status bar - handle empty board case

<template>

    {{doneItemsCount}} done |
    {{pendingItemsCount}} pending
    | {{progressCount}}% completed

</template>


replace default text with ‘required’

replace default text with ‘required’

<template>

      <svg v-if="showarrow" role="presentation" src="../../icons/back.svg"></svg>
      {{ text }}

</template>


Pagination logic.

Pagination logic.

<template>

 2 ? 'pure-u-1-2' : 'pure-u-1-1 align-center'">
        <router-link :to="editClassLink">
          <icon-button
            :text="$tr('backToClassDetails')"
            :primary="false">
            <mat-svg category="navigation" name="arrow_back"/>
          </icon-button>
        </router-link>

 2 ? 'pure-u-1-2 align-right' : 'pure-u-1-1 align-center'">
        <icon-button
          :text="$tr('enrollSelectedUsers')"
          :primary="true"
          @click="$refs.confirmation.open()"
          :disabled="selectedUsers.length === 0">
          <mat-svg category="navigation" name="check"/>
        </icon-button>

    <ui-confirm
      ref="confirmation"
      @confirm="enrollLearners"
      :closeOnConfirm="false"
      :title="$tr('confirmEnrollment')"
      :confirmButtonText="$tr('yesEnrollUsers')"
      :denyButtonText="$tr('noGoBack')">
      {{ $tr('areYouSure') }} {{ className }}?

{{ getUsername(userId) }}

    </ui-confirm>

{{ $tr('selectLearners') }} {{ className }}

{{ $tr('showingAllUnassigned') }}

    <textbox
      :placeholder="$tr('searchForUser')"
      :aria-label="$tr('searchForUser')"
      v-model="filterInput"
      type="search"
      @input="pageNum = 1"
      class="search-box"/>

    <ui-switch
      name="showSelectedUsers"
      :label="$tr('selectedUsers')"
      v-model="showSelectedUsers"
      class="switch"/>

{{ $tr('noUsersExist') }}

{{ $tr('noUsersSelected') }}

{{ $tr('noUsersMatch') }} "{{ filterInput }}"

        {{ $tr('showing') }} {{ visibleStartRange }} - {{ visibleEndRange }}
        {{ $tr('of') }} {{$tr('numLearners', {count: numFilteredUsers}) }}
        {{ $tr('thatMatch') }} "{{ filterInput }}"

          {{ $tr('name') }}
          {{ $tr('username') }}

          <input type="checkbox" :id="learner.id" :value="learner.id" v-model="selectedUsers">
          {{ learner.full_name }}
          {{ learner.username }}

     1" class="pagination">
      <ui-icon-button
        type="secondary"
        color="default"
        icon="chevron_left"
        :ariaLabel="$tr('previousResults')"
        :disabled="pageNum === 1"
        @click="goToPage(pageNum - 1)"/>
      <icon-button
        v-for="page in numPages"
        :text="String(page)"
        :primary="false"
        :ariaLabel="`${$tr('goToPage')} ${page}`"
        :disabled="pageNum === page"
        @click="goToPage(page)"
        v-if="windowSize.breakpoint > 2 && pageWithinRange(page)"/>
      <ui-icon-button
        type="secondary"
        color="default"
        icon="chevron_right"
        :ariaLabel="$tr('nextResults')"
        :disabled="pageNum === numPages"
        @click="goToPage(pageNum + 1)"/>

{{ $tr('createAndEnroll') }}

{{ $tr('enrollSomeone') }}

      <icon-button
        :text="$tr('createNewUser')"
        :primary="false"
        @click="openCreateUserModal">
        <mat-svg category="content" name="add"/>
      </icon-button>

      <user-create-modal
        v-if="createUserModalOpen"
        @close="closeCreateUserModal"/>

</template>

<template>

 2 ? 'pure-u-1-2' : 'pure-u-1-1 align-center'">
        <router-link :to="editClassLink">
          <icon-button
            :text="$tr('backToClassDetails')"
            :primary="false">
            <mat-svg category="navigation" name="arrow_back"/>
          </icon-button>
        </router-link>

 2 ? 'pure-u-1-2 align-right' : 'pure-u-1-1 align-center'">
        <icon-button
          :text="$tr('enrollSelectedUsers')"
          :primary="true"
          @click="$refs.confirmation.open()"
          :disabled="selectedUsers.length === 0">
          <mat-svg category="navigation" name="check"/>
        </icon-button>

    <ui-confirm
      ref="confirmation"
      @confirm="enrollLearners"
      :closeOnConfirm="false"
      :title="$tr('confirmEnrollment')"
      :confirmButtonText="$tr('yesEnrollUsers')"
      :denyButtonText="$tr('noGoBack')">
      {{ $tr('areYouSure') }} {{ className }}?

{{ getUsername(userId) }}

    </ui-confirm>

{{ $tr('selectLearners') }} {{ className }}

{{ $tr('showingAllUnassigned') }}

    <textbox
      :placeholder="$tr('searchForUser')"
      :aria-label="$tr('searchForUser')"
      v-model="filterInput"
      type="search"
      @input="pageNum = 1"
      class="search-box"/>

    <ui-switch
      name="showSelectedUsers"
      :label="$tr('selectedUsers')"
      v-model="showSelectedUsers"
      class="switch"/>

{{ $tr('noUsersExist') }}

{{ $tr('noUsersSelected') }}

{{ $tr('noUsersMatch') }} "{{ filterInput }}"

        {{ $tr('showing') }} {{ visibleStartRange }} - {{ visibleEndRange }}
        {{ $tr('of') }} {{$tr('numLearners', {count: numFilteredUsers}) }}
        {{ $tr('thatMatch') }} "{{ filterInput }}"

          {{ $tr('name') }}
          {{ $tr('username') }}

          <input type="checkbox" :id="learner.id" :value="learner.id" v-model="selectedUsers">
          {{ learner.full_name }}
          {{ learner.username }}

 1" class="pagination">
      <ui-icon-button
        type="secondary"
        color="default"
        icon="chevron_left"
        :ariaLabel="$tr('previousResults')"
        :disabled="pageNum === 1"
        @click="goToPage(pageNum - 1)"/>
      <icon-button
        v-for="page in numPages"
        :text="String(page)"
        :primary="false"
        :ariaLabel="`${$tr('goToPage')} ${page}`"
        :disabled="pageNum === page"
        @click="goToPage(page)"/>
      <ui-icon-button
        type="secondary"
        color="default"
        icon="chevron_right"
        :ariaLabel="$tr('nextResults')"
        :disabled="pageNum === numPages"
        @click="goToPage(pageNum + 1)"/>

{{ $tr('createAndEnroll') }}

{{ $tr('enrollSomeone') }}

      <icon-button
        :text="$tr('createNewUser')"
        :primary="false"
        @click="openCreateUserModal">
        <mat-svg category="content" name="add"/>
      </icon-button>

      <user-create-modal
        v-if="createUserModalOpen"
        @close="closeCreateUserModal"/>

</template>


perfect tagsPageOpened move to view

perfect tagsPageOpened move to view

<template>

            <Dropdown @on-click="handleTagsOption">
                <Button size="small" type="primary">
                    标签选项
                    <Icon type="arrow-down-b"></Icon>
                </Button>
                <DropdownMenu slot="list">
                    <DropdownItem name="clearAll">关闭所有</DropdownItem>
                    <DropdownItem name="clearOthers">关闭其他</DropdownItem>
                </DropdownMenu>
            </Dropdown>

            <transition-group name="taglist-moving-animation">
                <Tag
                    type="dot"
                    v-for="(item, index) in pageTagsList"
                    ref="tagsPageOpened"
                    :key="item.name"
                    :name="item.name"
                    @on-close="closePage"
                    @click.native="linkTo(item)"
                    :closable="item.name==='home_index'?false:true"
                    :color="item.children?(item.children[0].name===currentPageName?'blue':'default'):(item.name===currentPageName?'blue':'default')"
                >{{ item.title }}</Tag>
            </transition-group>

</template>


Give the `KModal` fade transition a more specific name. (#6348)

Give the `KModal` fade transition a more specific name.

<template>

  <!-- Accessibility properties for the overlay -->
  <transition name="modal-fade" appear>
    <div
      id="modal-window"
      ref="modal-overlay"
      class="modal-overlay"
      @keyup.esc.stop="emitCancelEvent"
      @keyup.enter="handleEnter"
    >
      <div
        ref="modal"
        class="modal"
        :tabindex="0"
        role="dialog"
        aria-labelledby="modal-title"
        :class="size"
        :style="[ modalSizeStyles, { background: $themeTokens.surface } ]"
      >

        <!-- Modal Title -->
        <h1
          id="modal-title"
          ref="title"
          class="title"
        >
          {{ title }}
          <!-- Accessible error reporting per @radina -->
          <span
            v-if="hasError"
            class="visuallyhidden"
          >
            {{ $tr('errorAlert', { title }) }}
          </span>
        </h1>

        <!-- Stop propagation of enter key to prevent the submit event from being emitted twice -->
        <form
          class="form"
          @submit.prevent="emitSubmitEvent"
          @keyup.enter.stop
        >
          <!-- Default slot for content -->
          <div
            ref="content"
            class="content"
            :style="[ contentSectionMaxHeight, scrollShadow ? {
              borderTop: `1px solid ${$themeTokens.fineLine}`,
              borderBottom: `1px solid ${$themeTokens.fineLine}`,
            } : {} ]"
            :class="{ 'scroll-shadow': scrollShadow }"
          >
            <slot></slot>
          </div>

          <div
            ref="actions"
            class="actions"
          >
            <!-- Slot for buttons -->
            <slot
              v-if="$slots.actions"
              name="actions"
            >
            </slot>
            <template v-else>
              <KButton
                v-if="cancelText"
                name="cancel"
                :text="cancelText"
                appearance="flat-button"
                :disabled="cancelDisabled || $attrs.disabled"
                @click="emitCancelEvent"
              />
              <KButton
                v-if="submitText"
                name="submit"
                :text="submitText"
                :primary="true"
                :disabled="submitDisabled || $attrs.disabled"
                type="submit"
              />
            </template>
          </div>
        </form>
      </div>
    </div>
  </transition>

</template>

<script>

  import logger from 'kolibri.lib.logging';
  import KResponsiveWindowMixin from 'kolibri-components/src/KResponsiveWindowMixin';
  import debounce from 'lodash/debounce';

  const logging = logger.getLogger(__filename);

  /**
   * Used to focus attention on a singular action/task
   */
  export default {
    name: 'KModal',
    mixins: [KResponsiveWindowMixin],
    props: {
      /**
       * The title of the modal
       */
      title: {
        type: String,
        required: true,
      },
      /**
       * The text of the submit button
       */
      submitText: {
        type: String,
        required: false,
      },
      /**
       * The text of the cancel button
       */
      cancelText: {
        type: String,
        required: false,
      },
      /**
       * Disable the submit button
       */
      submitDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * Disable the cancel button
       */
      cancelDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * How wide the modal should be.
       * Small - 300 px.
       * Medium - 450px.
       * Large - 100%.
       */
      size: {
        type: String,
        required: false,
        default: 'medium',
        validator(val) {
          return ['small', 'medium', 'large'].includes(val);
        },
      },
      /**
       * Toggles error message indicator in title
       */
      hasError: {
        type: Boolean,
        default: false,
      },
    },
    data() {
      return {
        lastFocus: null,
        maxContentHeight: '1000',
        scrollShadow: false,
        delayedEnough: false,
      };
    },
    computed: {
      modalSizeStyles() {
        return {
          'max-width': `${this.maxModalWidth - 32}px`,
          'max-height': `${this.windowHeight - 32}px`,
        };
      },
      maxModalWidth() {
        if (this.windowWidth < 1000) {
          return this.windowWidth;
        }
        return 1000;
      },
      contentSectionMaxHeight() {
        return { 'max-height': `${this.maxContentHeight}px` };
      },
    },
    created() {
      if (this.$props.cancelText && !this.$listeners.cancel) {
        logging.warn(
          'A "cancelText" has been set, but there is no "cancel" listener. The "cancel" button may not work correctly.'
        );
      }
      if (this.$props.submitText && !this.$listeners.submit) {
        logging.warn(
          'A "submitText" has been set, but there is no "submit" listener. The "submit" button may not work correctly.'
        );
      }
    },
    beforeMount() {
      this.lastFocus = document.activeElement;
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.modal && !this.$refs.modal.contains(document.activeElement)) {
          this.focusModal();
        }
      });
      window.addEventListener('focus', this.focusElementTest, true);
      window.addEventListener('scroll', this.preventScroll, true);
      window.setTimeout(() => (this.delayedEnough = true), 500);
    },
    updated() {
      this.updateContentSectionStyle();
    },
    destroyed() {
      window.removeEventListener('focus', this.focusElementTest, true);
      window.removeEventListener('scroll', this.preventScroll, true);
      // Wait for events to finish propagating before changing the focus.
      // Otherwise the `lastFocus` item receives events such as 'enter'.
      window.setTimeout(() => this.lastFocus.focus());
    },
    methods: {
      /**
       * Calculate the max-height of the content section of the modal
       * If there is not enough vertical space, create a vertically scrollable area and a
       * scroll shadow
       */
      updateContentSectionStyle: debounce(function() {
        if (this.$refs.title && this.$refs.actions) {
          this.maxContentHeight =
            this.windowHeight -
            this.$refs.title.clientHeight -
            this.$refs.actions.clientHeight -
            32;
          this.scrollShadow = this.maxContentHeight < this.$refs.content.scrollHeight;
        }
      }, 50),
      emitCancelEvent() {
        if (!this.cancelDisabled) {
          // Emitted when the cancel button is clicked or the esc key is pressed
          this.$emit('cancel');
        }
      },
      emitSubmitEvent() {
        if (!this.submitDisabled) {
          // Emitted when the submit button or the enter key is pressed
          this.$emit('submit');
        }
      },
      handleEnter() {
        if (this.delayedEnough) {
          this.emitSubmitEvent();
        }
      },
      focusModal() {
        this.$refs.modal.focus();
      },
      focusElementTest(event) {
        // switching apps - not relevant
        if (event.target === window) {
          return;
        }
        // not sure when this would be true
        if (!this.$refs.modal) {
          return;
        }
        // addresses https://github.com/learningequality/kolibri/issues/3824
        if (
          event.target === this.$refs.modal ||
          this.$refs.modal.contains(event.target.activeElement)
        ) {
          return;
        }
        // focus has escaped the modal - put it back!
        if (!this.$refs.modal.contains(event.target)) {
          this.focusModal();
        }
      },
      preventScroll(event) {
        event.preventDefault();
      },
    },
    $trs: {
      // error alerts
      errorAlert: 'Error in { title }',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 24;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    background-attachment: fixed;
    transition: opacity $core-time ease;
  }

  // TODO: margins for stacked buttons.
  .modal {
    @extend %dropshadow-16dp;

    position: absolute;
    top: 50%;
    left: 50%;
    margin: 0 auto;
    overflow-y: auto;
    border-radius: $radius;
    transform: translate(-50%, -50%);

    &:focus {
      outline: none;
    }
  }

  .form {
    @extend %momentum-scroll;
  }

  .modal-fade-enter-active,
  .modal-fade-leave-active {
    transition: all $core-time ease;
  }

  .modal-fade-enter,
  .modal-fade-leave-active {
    opacity: 0;
  }

  .title {
    padding: 24px;
    margin: 0;
    font-size: 24px;
  }

  .content {
    padding: 0 24px;
    overflow-y: auto;
  }

  .scroll-shadow {
    background: linear-gradient(white 30%, hsla(0, 0%, 100%, 0)),
      linear-gradient(hsla(0, 0%, 100%, 0) 10px, white 70%) bottom,
      radial-gradient(at top, rgba(0, 0, 0, 0.2), transparent 70%),
      radial-gradient(at bottom, rgba(0, 0, 0, 0.2), transparent 70%) bottom;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
    background-size: 100% 20px, 100% 20px, 100% 10px, 100% 10px;
  }

  .actions {
    padding: 24px;
    text-align: right;
    button {
      margin: 0;
    }
  }

  .actions button:last-of-type {
    margin-left: 16px;
  }

  .small {
    width: 300px;
  }

  .medium {
    width: 450px;
  }

  .large {
    width: 100%;
  }

</style>

<template>

  <!-- Accessibility properties for the overlay -->
  <transition name="fade" appear>
    <div
      id="modal-window"
      ref="modal-overlay"
      class="modal-overlay"
      @keyup.esc.stop="emitCancelEvent"
      @keyup.enter="handleEnter"
    >
      <div
        ref="modal"
        class="modal"
        :tabindex="0"
        role="dialog"
        aria-labelledby="modal-title"
        :class="size"
        :style="[ modalSizeStyles, { background: $themeTokens.surface } ]"
      >

        <!-- Modal Title -->
        <h1
          id="modal-title"
          ref="title"
          class="title"
        >
          {{ title }}
          <!-- Accessible error reporting per @radina -->
          <span
            v-if="hasError"
            class="visuallyhidden"
          >
            {{ $tr('errorAlert', { title }) }}
          </span>
        </h1>

        <!-- Stop propagation of enter key to prevent the submit event from being emitted twice -->
        <form
          class="form"
          @submit.prevent="emitSubmitEvent"
          @keyup.enter.stop
        >
          <!-- Default slot for content -->
          <div
            ref="content"
            class="content"
            :style="[ contentSectionMaxHeight, scrollShadow ? {
              borderTop: `1px solid ${$themeTokens.fineLine}`,
              borderBottom: `1px solid ${$themeTokens.fineLine}`,
            } : {} ]"
            :class="{ 'scroll-shadow': scrollShadow }"
          >
            <slot></slot>
          </div>

          <div
            ref="actions"
            class="actions"
          >
            <!-- Slot for buttons -->
            <slot
              v-if="$slots.actions"
              name="actions"
            >
            </slot>
            <template v-else>
              <KButton
                v-if="cancelText"
                name="cancel"
                :text="cancelText"
                appearance="flat-button"
                :disabled="cancelDisabled || $attrs.disabled"
                @click="emitCancelEvent"
              />
              <KButton
                v-if="submitText"
                name="submit"
                :text="submitText"
                :primary="true"
                :disabled="submitDisabled || $attrs.disabled"
                type="submit"
              />
            </template>
          </div>
        </form>
      </div>
    </div>
  </transition>

</template>

<script>

  import logger from 'kolibri.lib.logging';
  import KResponsiveWindowMixin from 'kolibri-components/src/KResponsiveWindowMixin';
  import debounce from 'lodash/debounce';

  const logging = logger.getLogger(__filename);

  /**
   * Used to focus attention on a singular action/task
   */
  export default {
    name: 'KModal',
    mixins: [KResponsiveWindowMixin],
    props: {
      /**
       * The title of the modal
       */
      title: {
        type: String,
        required: true,
      },
      /**
       * The text of the submit button
       */
      submitText: {
        type: String,
        required: false,
      },
      /**
       * The text of the cancel button
       */
      cancelText: {
        type: String,
        required: false,
      },
      /**
       * Disable the submit button
       */
      submitDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * Disable the cancel button
       */
      cancelDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * How wide the modal should be.
       * Small - 300 px.
       * Medium - 450px.
       * Large - 100%.
       */
      size: {
        type: String,
        required: false,
        default: 'medium',
        validator(val) {
          return ['small', 'medium', 'large'].includes(val);
        },
      },
      /**
       * Toggles error message indicator in title
       */
      hasError: {
        type: Boolean,
        default: false,
      },
    },
    data() {
      return {
        lastFocus: null,
        maxContentHeight: '1000',
        scrollShadow: false,
        delayedEnough: false,
      };
    },
    computed: {
      modalSizeStyles() {
        return {
          'max-width': `${this.maxModalWidth - 32}px`,
          'max-height': `${this.windowHeight - 32}px`,
        };
      },
      maxModalWidth() {
        if (this.windowWidth < 1000) {
          return this.windowWidth;
        }
        return 1000;
      },
      contentSectionMaxHeight() {
        return { 'max-height': `${this.maxContentHeight}px` };
      },
    },
    created() {
      if (this.$props.cancelText && !this.$listeners.cancel) {
        logging.warn(
          'A "cancelText" has been set, but there is no "cancel" listener. The "cancel" button may not work correctly.'
        );
      }
      if (this.$props.submitText && !this.$listeners.submit) {
        logging.warn(
          'A "submitText" has been set, but there is no "submit" listener. The "submit" button may not work correctly.'
        );
      }
    },
    beforeMount() {
      this.lastFocus = document.activeElement;
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.modal && !this.$refs.modal.contains(document.activeElement)) {
          this.focusModal();
        }
      });
      window.addEventListener('focus', this.focusElementTest, true);
      window.addEventListener('scroll', this.preventScroll, true);
      window.setTimeout(() => (this.delayedEnough = true), 500);
    },
    updated() {
      this.updateContentSectionStyle();
    },
    destroyed() {
      window.removeEventListener('focus', this.focusElementTest, true);
      window.removeEventListener('scroll', this.preventScroll, true);
      // Wait for events to finish propagating before changing the focus.
      // Otherwise the `lastFocus` item receives events such as 'enter'.
      window.setTimeout(() => this.lastFocus.focus());
    },
    methods: {
      /**
       * Calculate the max-height of the content section of the modal
       * If there is not enough vertical space, create a vertically scrollable area and a
       * scroll shadow
       */
      updateContentSectionStyle: debounce(function() {
        if (this.$refs.title && this.$refs.actions) {
          this.maxContentHeight =
            this.windowHeight -
            this.$refs.title.clientHeight -
            this.$refs.actions.clientHeight -
            32;
          this.scrollShadow = this.maxContentHeight < this.$refs.content.scrollHeight;
        }
      }, 50),
      emitCancelEvent() {
        if (!this.cancelDisabled) {
          // Emitted when the cancel button is clicked or the esc key is pressed
          this.$emit('cancel');
        }
      },
      emitSubmitEvent() {
        if (!this.submitDisabled) {
          // Emitted when the submit button or the enter key is pressed
          this.$emit('submit');
        }
      },
      handleEnter() {
        if (this.delayedEnough) {
          this.emitSubmitEvent();
        }
      },
      focusModal() {
        this.$refs.modal.focus();
      },
      focusElementTest(event) {
        // switching apps - not relevant
        if (event.target === window) {
          return;
        }
        // not sure when this would be true
        if (!this.$refs.modal) {
          return;
        }
        // addresses https://github.com/learningequality/kolibri/issues/3824
        if (
          event.target === this.$refs.modal ||
          this.$refs.modal.contains(event.target.activeElement)
        ) {
          return;
        }
        // focus has escaped the modal - put it back!
        if (!this.$refs.modal.contains(event.target)) {
          this.focusModal();
        }
      },
      preventScroll(event) {
        event.preventDefault();
      },
    },
    $trs: {
      // error alerts
      errorAlert: 'Error in { title }',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 24;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    background-attachment: fixed;
    transition: opacity $core-time ease;
  }

  // TODO: margins for stacked buttons.
  .modal {
    @extend %dropshadow-16dp;

    position: absolute;
    top: 50%;
    left: 50%;
    margin: 0 auto;
    overflow-y: auto;
    border-radius: $radius;
    transform: translate(-50%, -50%);

    &:focus {
      outline: none;
    }
  }

  .form {
    @extend %momentum-scroll;
  }

  .fade-enter-active,
  .fade-leave-active {
    transition: all $core-time ease;
  }

  .fade-enter,
  .fade-leave-active {
    opacity: 0;
  }

  .title {
    padding: 24px;
    margin: 0;
    font-size: 24px;
  }

  .content {
    padding: 0 24px;
    overflow-y: auto;
  }

  .scroll-shadow {
    background: linear-gradient(white 30%, hsla(0, 0%, 100%, 0)),
      linear-gradient(hsla(0, 0%, 100%, 0) 10px, white 70%) bottom,
      radial-gradient(at top, rgba(0, 0, 0, 0.2), transparent 70%),
      radial-gradient(at bottom, rgba(0, 0, 0, 0.2), transparent 70%) bottom;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
    background-size: 100% 20px, 100% 20px, 100% 10px, 100% 10px;
  }

  .actions {
    padding: 24px;
    text-align: right;
    button {
      margin: 0;
    }
  }

  .actions button:last-of-type {
    margin-left: 16px;
  }

  .small {
    width: 300px;
  }

  .medium {
    width: 450px;
  }

  .large {
    width: 100%;
  }

</style>


use min-height to ensure no scroll on small modals

<template>

  <!-- Accessibility properties for the overlay -->
  <transition name="fade" appear>
    <div
      id="modal-window"
      ref="modal-overlay"
      class="modal-overlay"
      @keyup.esc.stop="emitCancelEvent"
      @keyup.enter="handleEnter"
    >
      <div
        ref="modal"
        class="modal"
        :tabindex="0"
        role="dialog"
        aria-labelledby="modal-title"
        :class="size"
        :style="[ modalSizeStyles, { background: $themeTokens.surface } ]"
      >

        <!-- Modal Title -->
        <h1
          id="modal-title"
          ref="title"
          class="title"
        >
          {{ title }}
          <!-- Accessible error reporting per @radina -->
          <span
            v-if="hasError"
            class="visuallyhidden"
          >
            {{ $tr('errorAlert', { title }) }}
          </span>
        </h1>

        <!-- Stop propagation of enter key to prevent the submit event from being emitted twice -->
        <form
          class="form"
          @submit.prevent="emitSubmitEvent"
          @keyup.enter.stop
        >
          <!-- Default slot for content -->
          <div
            ref="content"
            class="content"
            :style="[ contentSectionMaxHeight, scrollShadow ? {
              borderTop: `1px solid ${$themeTokens.fineLine}`,
              borderBottom: `1px solid ${$themeTokens.fineLine}`,
            } : {} ]"
            :class="{ 'scroll-shadow': scrollShadow }"
          >
            <slot></slot>
          </div>

          <div
            ref="actions"
            class="actions"
          >
            <!-- Slot for buttons -->
            <slot
              v-if="$slots.actions"
              name="actions"
            >
            </slot>
            <template v-else>
              <KButton
                v-if="cancelText"
                name="cancel"
                :text="cancelText"
                appearance="flat-button"
                :disabled="cancelDisabled || $attrs.disabled"
                @click="emitCancelEvent"
              />
              <KButton
                v-if="submitText"
                name="submit"
                :text="submitText"
                :primary="true"
                :disabled="submitDisabled || $attrs.disabled"
                type="submit"
              />
            </template>
          </div>
        </form>
      </div>
    </div>
  </transition>

</template>

<script>

  import logger from 'kolibri.lib.logging';
  import KResponsiveWindowMixin from 'kolibri-components/src/KResponsiveWindowMixin';
  import debounce from 'lodash/debounce';

  const logging = logger.getLogger(__filename);

  /**
   * Used to focus attention on a singular action/task
   */
  export default {
    name: 'KModal',
    mixins: [KResponsiveWindowMixin],
    props: {
      /**
       * The title of the modal
       */
      title: {
        type: String,
        required: true,
      },
      /**
       * The text of the submit button
       */
      submitText: {
        type: String,
        required: false,
      },
      /**
       * The text of the cancel button
       */
      cancelText: {
        type: String,
        required: false,
      },
      /**
       * Disable the submit button
       */
      submitDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * Disable the cancel button
       */
      cancelDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * How wide the modal should be.
       * Small - 300 px.
       * Medium - 450px.
       * Large - 100%.
       */
      size: {
        type: String,
        required: false,
        default: 'medium',
        validator(val) {
          return ['small', 'medium', 'large'].includes(val);
        },
      },
      /**
       * Toggles error message indicator in title
       */
      hasError: {
        type: Boolean,
        default: false,
      },
    },
    data() {
      return {
        lastFocus: null,
        maxContentHeight: '1000',
        contentHeight: 0,
        scrollShadow: false,
        delayedEnough: false,
      };
    },
    computed: {
      modalSizeStyles() {
        return {
          'max-width': `${this.maxModalWidth - 32}px`,
          'max-height': `${this.windowHeight - 32}px`,
        };
      },
      maxModalWidth() {
        if (this.windowWidth < 1000) {
          return this.windowWidth;
        }
        return 1000;
      },
      contentSectionMaxHeight() {
        return {
          'max-height': `${this.maxContentHeight}px`,
          'min-height': `${this.contentHeight}px`,
        };
      },
    },
    created() {
      if (this.$props.cancelText && !this.$listeners.cancel) {
        logging.warn(
          'A "cancelText" has been set, but there is no "cancel" listener. The "cancel" button may not work correctly.'
        );
      }
      if (this.$props.submitText && !this.$listeners.submit) {
        logging.warn(
          'A "submitText" has been set, but there is no "submit" listener. The "submit" button may not work correctly.'
        );
      }
    },
    beforeMount() {
      this.lastFocus = document.activeElement;
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.modal && !this.$refs.modal.contains(document.activeElement)) {
          this.focusModal();
        }
      });
      window.addEventListener('focus', this.focusElementTest, true);
      window.addEventListener('scroll', this.preventScroll, true);
      window.setTimeout(() => (this.delayedEnough = true), 500);
    },
    updated() {
      this.updateContentSectionStyle();
    },
    destroyed() {
      window.removeEventListener('focus', this.focusElementTest, true);
      window.removeEventListener('scroll', this.preventScroll, true);
      // Wait for events to finish propagating before changing the focus.
      // Otherwise the `lastFocus` item receives events such as 'enter'.
      window.setTimeout(() => this.lastFocus.focus());
    },
    methods: {
      /**
       * Calculate the max-height of the content section of the modal
       * If there is not enough vertical space, create a vertically scrollable area and a
       * scroll shadow
       */
      updateContentSectionStyle: debounce(function() {
        if (this.$refs.title && this.$refs.actions) {
          if (Math.abs(this.$refs.content.scrollHeight - this.contentHeight) >= 8) {
            this.contentHeight = this.$refs.content.scrollHeight;
          }
          const maxContentHeightCheck =
            this.windowHeight -
            this.$refs.title.clientHeight -
            this.$refs.actions.clientHeight -
            32;
          // to prevent max height from toggling between pixels
          // we set a threshold of how many pixels the height should change before we update
          if (Math.abs(maxContentHeightCheck - this.maxContentHeight) >= 8) {
            this.maxContentHeight = maxContentHeightCheck;
            this.scrollShadow = this.maxContentHeight < this.$refs.content.scrollHeight;
          }
        }
      }, 50),
      emitCancelEvent() {
        if (!this.cancelDisabled) {
          // Emitted when the cancel button is clicked or the esc key is pressed
          this.$emit('cancel');
        }
      },
      emitSubmitEvent() {
        if (!this.submitDisabled) {
          // Emitted when the submit button or the enter key is pressed
          this.$emit('submit');
        }
      },
      handleEnter() {
        if (this.delayedEnough) {
          this.emitSubmitEvent();
        }
      },
      focusModal() {
        this.$refs.modal.focus();
      },
      focusElementTest(event) {
        // switching apps - not relevant
        if (event.target === window) {
          return;
        }
        // not sure when this would be true
        if (!this.$refs.modal) {
          return;
        }
        // addresses https://github.com/learningequality/kolibri/issues/3824
        if (
          event.target === this.$refs.modal ||
          this.$refs.modal.contains(event.target.activeElement)
        ) {
          return;
        }
        // focus has escaped the modal - put it back!
        if (!this.$refs.modal.contains(event.target)) {
          this.focusModal();
        }
      },
      preventScroll(event) {
        event.preventDefault();
      },
    },
    $trs: {
      // error alerts
      errorAlert: 'Error in { title }',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 24;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    background-attachment: fixed;
    transition: opacity $core-time ease;
  }

  // TODO: margins for stacked buttons.
  .modal {
    @extend %dropshadow-16dp;

    position: absolute;
    top: 50%;
    left: 50%;
    margin: 0 auto;
    overflow-y: auto;
    border-radius: $radius;
    transform: translate(-50%, -50%);

    &:focus {
      outline: none;
    }
  }

  .form {
    @extend %momentum-scroll;
  }

  .fade-enter-active,
  .fade-leave-active {
    transition: all $core-time ease;
  }

  .fade-enter,
  .fade-leave-active {
    opacity: 0;
  }

  .title {
    padding: 24px;
    margin: 0;
    font-size: 24px;
  }

  .content {
    padding: 0 24px;
    overflow-y: auto;
  }

  .scroll-shadow {
    background: linear-gradient(white 30%, hsla(0, 0%, 100%, 0)),
      linear-gradient(hsla(0, 0%, 100%, 0) 10px, white 70%) bottom,
      radial-gradient(at top, rgba(0, 0, 0, 0.2), transparent 70%),
      radial-gradient(at bottom, rgba(0, 0, 0, 0.2), transparent 70%) bottom;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
    background-size: 100% 20px, 100% 20px, 100% 10px, 100% 10px;
  }

  .actions {
    padding: 24px;
    text-align: right;
    button {
      margin: 0;
    }
  }

  .actions button:last-of-type {
    margin-left: 16px;
  }

  .small {
    width: 300px;
  }

  .medium {
    width: 450px;
  }

  .large {
    width: 100%;
  }

</style>

<template>

  <!-- Accessibility properties for the overlay -->
  <transition name="fade" appear>
    <div
      id="modal-window"
      ref="modal-overlay"
      class="modal-overlay"
      @keyup.esc.stop="emitCancelEvent"
      @keyup.enter="handleEnter"
    >
      <div
        ref="modal"
        class="modal"
        :tabindex="0"
        role="dialog"
        aria-labelledby="modal-title"
        :class="size"
        :style="[ modalSizeStyles, { background: $themeTokens.surface } ]"
      >

        <!-- Modal Title -->
        <h1
          id="modal-title"
          ref="title"
          class="title"
        >
          {{ title }}
          <!-- Accessible error reporting per @radina -->
          <span
            v-if="hasError"
            class="visuallyhidden"
          >
            {{ $tr('errorAlert', { title }) }}
          </span>
        </h1>

        <!-- Stop propagation of enter key to prevent the submit event from being emitted twice -->
        <form
          class="form"
          @submit.prevent="emitSubmitEvent"
          @keyup.enter.stop
        >
          <!-- Default slot for content -->
          <div
            ref="content"
            class="content"
            :style="[ contentSectionMaxHeight, scrollShadow ? {
              borderTop: `1px solid ${$themeTokens.fineLine}`,
              borderBottom: `1px solid ${$themeTokens.fineLine}`,
            } : {} ]"
            :class="{ 'scroll-shadow': scrollShadow }"
          >
            <slot></slot>
          </div>

          <div
            ref="actions"
            class="actions"
          >
            <!-- Slot for buttons -->
            <slot
              v-if="$slots.actions"
              name="actions"
            >
            </slot>
            <template v-else>
              <KButton
                v-if="cancelText"
                name="cancel"
                :text="cancelText"
                appearance="flat-button"
                :disabled="cancelDisabled || $attrs.disabled"
                @click="emitCancelEvent"
              />
              <KButton
                v-if="submitText"
                name="submit"
                :text="submitText"
                :primary="true"
                :disabled="submitDisabled || $attrs.disabled"
                type="submit"
              />
            </template>
          </div>
        </form>
      </div>
    </div>
  </transition>

</template>

<script>

  import logger from 'kolibri.lib.logging';
  import KResponsiveWindowMixin from 'kolibri-components/src/KResponsiveWindowMixin';
  import debounce from 'lodash/debounce';

  const logging = logger.getLogger(__filename);

  /**
   * Used to focus attention on a singular action/task
   */
  export default {
    name: 'KModal',
    mixins: [KResponsiveWindowMixin],
    props: {
      /**
       * The title of the modal
       */
      title: {
        type: String,
        required: true,
      },
      /**
       * The text of the submit button
       */
      submitText: {
        type: String,
        required: false,
      },
      /**
       * The text of the cancel button
       */
      cancelText: {
        type: String,
        required: false,
      },
      /**
       * Disable the submit button
       */
      submitDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * Disable the cancel button
       */
      cancelDisabled: {
        type: Boolean,
        default: false,
      },
      /**
       * How wide the modal should be.
       * Small - 300 px.
       * Medium - 450px.
       * Large - 100%.
       */
      size: {
        type: String,
        required: false,
        default: 'medium',
        validator(val) {
          return ['small', 'medium', 'large'].includes(val);
        },
      },
      /**
       * Toggles error message indicator in title
       */
      hasError: {
        type: Boolean,
        default: false,
      },
    },
    data() {
      return {
        lastFocus: null,
        maxContentHeight: '1000',
        contentHeight: 0,
        scrollShadow: false,
        delayedEnough: false,
      };
    },
    computed: {
      modalSizeStyles() {
        return {
          'max-width': `${this.maxModalWidth - 32}px`,
          'max-height': `${this.windowHeight - 32}px`,
        };
      },
      maxModalWidth() {
        if (this.windowWidth < 1000) {
          return this.windowWidth;
        }
        return 1000;
      },
      contentSectionMaxHeight() {
        return {
          'max-height': `${this.maxContentHeight}px`,
          height: `${this.contentHeight}px`,
        };
      },
    },
    created() {
      if (this.$props.cancelText && !this.$listeners.cancel) {
        logging.warn(
          'A "cancelText" has been set, but there is no "cancel" listener. The "cancel" button may not work correctly.'
        );
      }
      if (this.$props.submitText && !this.$listeners.submit) {
        logging.warn(
          'A "submitText" has been set, but there is no "submit" listener. The "submit" button may not work correctly.'
        );
      }
    },
    beforeMount() {
      this.lastFocus = document.activeElement;
    },
    mounted() {
      this.$nextTick(() => {
        if (this.$refs.modal && !this.$refs.modal.contains(document.activeElement)) {
          this.focusModal();
        }
      });
      window.addEventListener('focus', this.focusElementTest, true);
      window.addEventListener('scroll', this.preventScroll, true);
      window.setTimeout(() => (this.delayedEnough = true), 500);
    },
    updated() {
      this.updateContentSectionStyle();
    },
    destroyed() {
      window.removeEventListener('focus', this.focusElementTest, true);
      window.removeEventListener('scroll', this.preventScroll, true);
      // Wait for events to finish propagating before changing the focus.
      // Otherwise the `lastFocus` item receives events such as 'enter'.
      window.setTimeout(() => this.lastFocus.focus());
    },
    methods: {
      /**
       * Calculate the max-height of the content section of the modal
       * If there is not enough vertical space, create a vertically scrollable area and a
       * scroll shadow
       */
      updateContentSectionStyle: debounce(function() {
        if (this.$refs.title && this.$refs.actions) {
          if (Math.abs(this.$refs.content.scrollHeight - this.contentHeight) >= 8) {
            this.contentHeight = this.$refs.content.scrollHeight;
          }
          const maxContentHeightCheck =
            this.windowHeight -
            this.$refs.title.clientHeight -
            this.$refs.actions.clientHeight -
            32;
          // to prevent max height from toggling between pixels
          // we set a threshold of how many pixels the height should change before we update
          if (Math.abs(maxContentHeightCheck - this.maxContentHeight) >= 8) {
            this.maxContentHeight = maxContentHeightCheck;
            this.scrollShadow = this.maxContentHeight < this.$refs.content.scrollHeight;
          }
        }
      }, 50),
      emitCancelEvent() {
        if (!this.cancelDisabled) {
          // Emitted when the cancel button is clicked or the esc key is pressed
          this.$emit('cancel');
        }
      },
      emitSubmitEvent() {
        if (!this.submitDisabled) {
          // Emitted when the submit button or the enter key is pressed
          this.$emit('submit');
        }
      },
      handleEnter() {
        if (this.delayedEnough) {
          this.emitSubmitEvent();
        }
      },
      focusModal() {
        this.$refs.modal.focus();
      },
      focusElementTest(event) {
        // switching apps - not relevant
        if (event.target === window) {
          return;
        }
        // not sure when this would be true
        if (!this.$refs.modal) {
          return;
        }
        // addresses https://github.com/learningequality/kolibri/issues/3824
        if (
          event.target === this.$refs.modal ||
          this.$refs.modal.contains(event.target.activeElement)
        ) {
          return;
        }
        // focus has escaped the modal - put it back!
        if (!this.$refs.modal.contains(event.target)) {
          this.focusModal();
        }
      },
      preventScroll(event) {
        event.preventDefault();
      },
    },
    $trs: {
      // error alerts
      errorAlert: 'Error in { title }',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 24;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    background-attachment: fixed;
    transition: opacity $core-time ease;
  }

  // TODO: margins for stacked buttons.
  .modal {
    @extend %dropshadow-16dp;

    position: absolute;
    top: 50%;
    left: 50%;
    margin: 0 auto;
    overflow-y: auto;
    border-radius: $radius;
    transform: translate(-50%, -50%);

    &:focus {
      outline: none;
    }
  }

  .form {
    @extend %momentum-scroll;
  }

  .fade-enter-active,
  .fade-leave-active {
    transition: all $core-time ease;
  }

  .fade-enter,
  .fade-leave-active {
    opacity: 0;
  }

  .title {
    padding: 24px;
    margin: 0;
    font-size: 24px;
  }

  .content {
    padding: 0 24px;
    overflow-y: auto;
  }

  .scroll-shadow {
    background: linear-gradient(white 30%, hsla(0, 0%, 100%, 0)),
      linear-gradient(hsla(0, 0%, 100%, 0) 10px, white 70%) bottom,
      radial-gradient(at top, rgba(0, 0, 0, 0.2), transparent 70%),
      radial-gradient(at bottom, rgba(0, 0, 0, 0.2), transparent 70%) bottom;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
    background-size: 100% 20px, 100% 20px, 100% 10px, 100% 10px;
  }

  .actions {
    padding: 24px;
    text-align: right;
    button {
      margin: 0;
    }
  }

  .actions button:last-of-type {
    margin-left: 16px;
  }

  .small {
    width: 300px;
  }

  .medium {
    width: 450px;
  }

  .large {
    width: 100%;
  }

</style>


Improved the reader sub-menu in the main navigation

Refs #78

<template>
	<v-app id="demyo">
		<v-navigation-drawer v-model="mainMenu" app>
			<v-list>
				<v-list-group>
					<template v-slot:activator>
						<v-list-item-icon>
							<LetterIcon letter="T" color="#0000ff" />
						</v-list-item-icon>
						<v-list-item-content>
							<v-list-item-title>TODO: reader</v-list-item-title>
						</v-list-item-content>
					</template>

					<v-list-item href="/TODO">
						<v-list-item-icon>
							<v-icon>mdi-heart</v-icon>
						</v-list-item-icon>
						<v-list-item-content>
							<v-list-item-title>{{ $t('menu.reader.faves') }}</v-list-item-title>
						</v-list-item-content>
					</v-list-item>
					<v-list-item href="/TODO">
						<v-list-item-icon>
							<v-icon>mdi-bookmark-multiple</v-icon>
						</v-list-item-icon>
						<v-list-item-content>
							<v-list-item-title>{{ $t('menu.reader.readingList') }}</v-list-item-title>
						</v-list-item-content>
					</v-list-item>
					<v-list-item href="/readers">
						<v-list-item-icon>
							<v-icon>mdi-account-convert</v-icon>
						</v-list-item-icon>
						<v-list-item-content>
							<v-list-item-title>{{ $t('menu.reader.switch') }}</v-list-item-title>
						</v-list-item-content>
					</v-list-item>
				</v-list-group>

				<v-divider />

				<v-list-item href="/">
					<v-list-item-icon>
						<v-icon>mdi-home</v-icon>
					</v-list-item-icon>
					<v-list-item-content>
						<v-list-item-title>{{ $t('menu.main.home') }}</v-list-item-title>
					</v-list-item-content>
				</v-list-item>

				<v-list-group v-for="section in menuItems" :key="section.title" v-model="section.active">
					<template v-slot:activator>
						<v-list-item-content>
							<v-list-item-title v-text="$t(section.title)" />
						</v-list-item-content>
					</template>

					<v-list-item v-for="item in section.subItems" :key="item.title" :href="item.url">
						<v-list-item-icon>
							<v-icon v-text="item.icon" />
						</v-list-item-icon>
						<v-list-item-content>
							<v-list-item-title v-text="item.title" />
						</v-list-item-content>
					</v-list-item>
				</v-list-group>
			</v-list>
		</v-navigation-drawer>
		<v-app-bar color="primary" dark app>

			<v-app-bar-nav-icon @click.stop="mainMenu = !mainMenu" />
			<v-toolbar-title>TODO: title</v-toolbar-title>
			<v-spacer />
			TODO: search
		</v-app-bar>

		<v-content>
			<HelloWorld />
			<v-footer color="secondary" inset dark>
				<v-col>
					TODO: codename
				</v-col>
			</v-footer>
		</v-content>
	</v-app>
</template>

<script>
import HelloWorld from './components/HelloWorld'
import LetterIcon from './components/LetterIcon'

export default {
	name: 'App',

	components: {
		HelloWorld,
		LetterIcon
	},

	data() {
		return {
			mainMenu: false,

			menuItems: [
				{
					title: 'menu.section.view',
					active: true,
					subItems: [
						{
							title: 'menu.series.browse',
							icon: 'mdi-layers-triple',
							url: '/series'
						},
						{
							title: 'menu.albums.browse',
							icon: 'mdi-book-open-variant',
							url: '/albums'
						},
						{
							title: 'menu.authors.browse',
							icon: 'mdi-account',
							url: '/authors'
						}
					]
				},
				{
					title: 'menu.section.edit',
					subItems: [
						{
							title: 'menu.albums.add',
							icon: 'mdi-book-open-variant',
							url: '/albums/add'
						},
						{
							title: 'menu.authors.add',
							icon: 'mdi-account-plus',
							url: '/authors/add'
						},
						{
							title: 'menu.series.add',
							icon: 'mdi-layers-plus',
							url: '/series/add'
						}
					]
				}
			]
		}
	}
}
</script>

<style lang="less">
html[lang], #demyo {
	font-family: -apple-system, BlinkMacSystemFont, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

#demyo .v-list-item__title {
	font-size: 14px;
}
</style>

<template>
	<v-app id="demyo">
		<v-navigation-drawer v-model="mainMenu" app>
			<v-list>
				<v-list-item href="/TODO">
					<v-list-item-icon>
						<LetterIcon letter="T" color="#0000ff" />
					</v-list-item-icon>
					<v-list-item-content>
						<v-list-item-title>TODO: reader</v-list-item-title>
					</v-list-item-content>
				</v-list-item>
				<v-list-item href="/">
					<v-list-item-icon>
						<v-icon>mdi-home</v-icon>
					</v-list-item-icon>
					<v-list-item-content>
						<v-list-item-title>{{ $t('menu.main.home') }}</v-list-item-title>
					</v-list-item-content>
				</v-list-item>

				<v-list-group v-for="section in menuItems" :key="section.title" v-model="section.active">
					<template v-slot:activator>
						<v-list-item-content>
							<v-list-item-title v-text="$t(section.title)" />
						</v-list-item-content>
					</template>

					<v-list-item v-for="item in section.subItems" :key="item.title" :href="item.url">
						<v-list-item-icon>
							<v-icon v-text="item.icon" />
						</v-list-item-icon>
						<v-list-item-content>
							<v-list-item-title v-text="item.title" />
						</v-list-item-content>
					</v-list-item>
				</v-list-group>
			</v-list>
		</v-navigation-drawer>
		<v-app-bar color="primary" dark app>

			<v-app-bar-nav-icon @click.stop="mainMenu = !mainMenu" />
			<v-toolbar-title>TODO: title</v-toolbar-title>
			<v-spacer />
			TODO: search
		</v-app-bar>

		<v-content>
			<HelloWorld />
			<v-footer color="secondary" inset dark>
				<v-col>
					TODO: codename
				</v-col>
			</v-footer>
		</v-content>
	</v-app>
</template>

<script>
import HelloWorld from './components/HelloWorld'
import LetterIcon from './components/LetterIcon'

export default {
	name: 'App',

	components: {
		HelloWorld,
		LetterIcon
	},

	data() {
		return {
			mainMenu: false,

			menuItems: [
				{
					title: 'menu.section.view',
					active: true,
					subItems: [
						{
							title: 'menu.series.browse',
							icon: 'mdi-layers-triple',
							url: '/series'
						},
						{
							title: 'menu.albums.browse',
							icon: 'mdi-book-open-variant',
							url: '/albums'
						},
						{
							title: 'menu.authors.browse',
							icon: 'mdi-account',
							url: '/authors'
						}
					]
				},
				{
					title: 'menu.section.edit',
					subItems: [
						{
							title: 'menu.albums.add',
							icon: 'mdi-book-open-variant',
							url: '/albums/add'
						},
						{
							title: 'menu.authors.add',
							icon: 'mdi-account-plus',
							url: '/authors/add'
						},
						{
							title: 'menu.series.add',
							icon: 'mdi-layers-plus',
							url: '/series/add'
						}
					]
				}
			]
		}
	}
}
</script>

<style lang="less">
html[lang], #demyo {
	font-family: -apple-system, BlinkMacSystemFont, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

#demyo .v-list-item__title {
	font-size: 14px;
}
</style>


add consistent right-padding

add consistent right-padding

<template>

  <div :class="`gutter-${windowSize.gutterWidth}`">
    <app-bar
      class="app-bar"
      :style="navOpenStyle"
      @toggleSideNav="navShown=!navShown"
      :title="topLevelPageName"
      :navShown="navShown"
      :height="baseMaterialIncrement">

        <slot name="app-bar-actions"/>

    </app-bar>

    <loading-spinner v-if="loading" class="loading-spinner-fixed"/>
    <div v-if="!loading" :style="{left: `${this.paddingForNav}px`, top: `${this.baseMaterialIncrement}px`}" class="content-container">
      <error-box v-if="error"/>
      <slot name="content"/>

    <slot name="extra"/>

</template>


Add in global right & left padding for mobile view

<template>

      <slot name="nav"></slot>

      <error-box v-show='error'></error-box>
      <slot name="above"></slot>

        <slot name="content"></slot>

      <slot name="below"></slot>

</template>


Changed chat margin back. Looks better on mobile this way

<template>
  <v-layout column justify-end class="mb-5">
    <chat-message :key="message.text" v-for="message in messages" :message="message" />
  </v-layout>
</template>

<script>
export default {
  props: ['messages']
}
</script>

<template>
  <v-layout column justify-end class="mb-2">
    <chat-message :key="message.text" v-for="message in messages" :message="message" />
  </v-layout>
</template>

<script>
export default {
  props: ['messages']
}
</script>


fix(grammar-history): fix collapsed whitespaces in grammar diff

Closes #149

<template>

History

          {{ grammar.created_at }}

          {{ grammar.updater.name }}
          Show

</template>


Removed not needed prop in example

Removed not needed prop in example

<template>
  <modal name="size-modal"
        transition="nice-modal-fade"
        :min-width="200"
        :min-height="200"
        :pivot-y="0.25"
        :adaptive="true"
        :scrollable="true"
        :reset="true"
        width="60%"
        height="auto"
        @before-open="beforeOpen"
        @before-close="beforeClose">

A new paragraph will be added every 5 sec to show how height scales.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla dictum purus egestas libero ornare venenatis. Maecenas pharetra tortor eu tortor imperdiet, a faucibus quam finibus. Nulla id lacinia quam. Praesent imperdiet sed magna non finibus. Aenean blandit, mauris vitae lacinia rutrum, nunc mi scelerisque sem, in laoreet sem lectus ut orci. Ut egestas nulla in vehicula feugiat. Vivamus tincidunt nisi vel risus dictum suscipit. Nulla id blandit mi, vulputate aliquam enim.

</modal>
</template>

<template>
  <modal name="size-modal"
        transition="nice-modal-fade"
        :min-width="200"
        :min-height="200"
        :pivot-y="0.25"
        :adaptive="true"
        :draggable="true"
        :scrollable="true"
        :reset="true"
        width="60%"
        height="auto"
        @before-open="beforeOpen"
        @before-close="beforeClose">

A new paragraph will be added every 5 sec to show how height scales.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla dictum purus egestas libero ornare venenatis. Maecenas pharetra tortor eu tortor imperdiet, a faucibus quam finibus. Nulla id lacinia quam. Praesent imperdiet sed magna non finibus. Aenean blandit, mauris vitae lacinia rutrum, nunc mi scelerisque sem, in laoreet sem lectus ut orci. Ut egestas nulla in vehicula feugiat. Vivamus tincidunt nisi vel risus dictum suscipit. Nulla id blandit mi, vulputate aliquam enim.

</modal>
</template>