informative cloak when restart is required

<template>
  <Modal :value="isVisible"
         title="Settings"
         @on-ok="closeSettingsModal"
         :closable="false"
         :mask-closable="false"
         okText="Add"
         :scrollable="true"
  >

Backlog

v{{currentVersion}}

    <updates-check-settings/>

    <database-location :settings="settings"/>

    <general-settings :settings="settings"/>

Setup board names and order

    <draggable :list="boardsLocal"
               :options="{handle: '.draggable'}"
               @change="boardOrderChanged">

          <Icon type="more"
                class="movable-icon"
          ></Icon>

        <Input v-model="board.label" style="width: 300px" @on-blur="saveBoards"/>
        <Tooltip content="Save as a JSON"
                 placement="left"
                 :transfer="true"
        >
          <Icon type="archive" class="download-icon" @click="openSaveDialog(board.id)"></Icon>
        </Tooltip>

    </draggable>

      <Button size="large" @click="closeSettingsModal">Close</Button>
      <transition name="fade">

          Restart required

      </transition>

    <transition name="fade">

App will restart in {{countDown}}

    </transition>
  </Modal>
</template>

<template>
  <Modal :value="isVisible"
         title="Settings"
         @on-ok="closeSettingsModal"
         :closable="false"
         :mask-closable="false"
         okText="Add"
         :scrollable="true"
  >

Backlog

v{{currentVersion}}

    <updates-check-settings/>

    <database-location :settings="settings"/>

    <general-settings :settings="settings"/>

Setup board names and order

    <draggable :list="boardsLocal"
               :options="{handle: '.draggable'}"
               @change="boardOrderChanged">

          <Icon type="more"
                class="movable-icon"
          ></Icon>

        <Input v-model="board.label" style="width: 300px" @on-blur="saveBoards"/>
        <Tooltip content="Save as a JSON"
                 placement="left"
                 :transfer="true"
        >
          <Icon type="archive" class="download-icon" @click="openSaveDialog(board.id)"></Icon>
        </Tooltip>

    </draggable>

      <Button size="large" @click="closeSettingsModal">Close</Button>
      <transition name="fade">

          Restart required

      </transition>

    <transition name="fade">

        App restarts within 3 seconds

    </transition>
  </Modal>
</template>


Adjust call icon position in participant list

If no menu icon is there the call icon is now on the right most side.
Replaced hack for vertical alignment with display: flex.

Signed-off-by: Vincent Petry <87e1f221a672a14a323e57bb65eaea19d3ed3804@nextcloud.com>

<template>

		<AvatarWrapper
			:id="computedId"
			:disable-tooltip="true"
			:size="44"
			:show-user-status="showUserStatus && !isSearched"
			:show-user-status-compact="false"
			:name="computedName"
			:source="participant.source || participant.actorType"
			:offline="isOffline" />

				{{ computedName }}
				({{ t('spreed', 'moderator') }})
				({{ t('spreed', 'guest') }})

				{{ statusMessage }}

			<Microphone
				v-if="callIcon === 'audio'"
				:size="24"
				decorative />
			<Phone
				v-if="callIcon === 'phone'"
				:size="24"
				decorative />
			<Video
				v-if="callIcon === 'video'"
				:size="24"
				decorative />

		<Actions
			v-if="canModerate && !isSearched"
			:aria-label="t('spreed', 'Participant settings')"
			class="participant-row__actions">
			<ActionButton v-if="canBeDemoted"
				icon="icon-rename"
				@click="demoteFromModerator">
				{{ t('spreed', 'Demote from moderator') }}
			</ActionButton>
			<ActionButton v-if="canBePromoted"
				icon="icon-rename"
				@click="promoteToModerator">
				{{ t('spreed', 'Promote to moderator') }}
			</ActionButton>
			<ActionButton
				icon="icon-delete"
				@click="removeParticipant">
				{{ t('spreed', 'Remove participant') }}
			</ActionButton>
		</Actions>

</template>


Add checkmark for selected users

Add checkmark for selected users

Signed-off-by: Marco Ambrosini <3397c7e210a9d1588196cfebb12b2c6b133f6934@pm.me>

<template>

			<Avatar
				:user="computedId"
				:display-name="computedName" />

		{{ computedName }}
		({{ t('spreed', 'moderator') }})
		<Actions v-if="canModerate && !isSearched" class="participant-row__utils">
			<ActionButton v-if="canBeDemoted"
				icon="icon-rename"
				@click="demoteFromModerator">
				{{ t('spreed', 'Demote from moderator') }}
			</ActionButton>
			<ActionButton v-if="canBePromoted"
				icon="icon-rename"
				@click="promoteToModerator">
				{{ t('spreed', 'Promote to moderator') }}
			</ActionButton>
			<ActionButton
				icon="icon-delete"
				@click="removeParticipant">
				{{ t('spreed', 'Remove participant') }}
			</ActionButton>
		</Actions>

</template>

<template>

			<Avatar
				:user="computedId"
				:display-name="computedName" />

		{{ computedName }}
		({{ t('spreed', 'moderator') }})
		<Actions v-if="canModerate && !isSearched" class="participant-row__actions">
			<ActionButton v-if="canBeDemoted"
				icon="icon-rename"
				@click="demoteFromModerator">
				{{ t('spreed', 'Demote from moderator') }}
			</ActionButton>
			<ActionButton v-if="canBePromoted"
				icon="icon-rename"
				@click="promoteToModerator">
				{{ t('spreed', 'Promote to moderator') }}
			</ActionButton>
			<ActionButton
				icon="icon-delete"
				@click="removeParticipant">
				{{ t('spreed', 'Remove participant') }}
			</ActionButton>
		</Actions>

</template>


First pass at Facility Name template

First pass at Facility Name template

<template>

  <form>
    <fieldset>
      <legend>
        <h1>
          Set facility name
        </h1>
      </legend>

      <k-textbox v-model="facilityName" label="Facility name"/>

      <k-button type="submit" :text="submitText" />

    </fieldset>
  </form>

</template>

<script>

  import kTextbox from 'kolibri.coreVue.components.kTextbox';
  import kButton from 'kolibri.coreVue.components.kButton';

  export default {
    name: 'facilityNameForm',
    props: {
      submitText: {
        type: String,
        required: true,
      },
    },
    components: {
      kTextbox,
      kButton,
    },
    data() {
      return {
        facilityName: '',
      };
    },
  };

</script>

<style lang="stylus" scoped></style>

<template>

  <form>
    Set facility name
    <button> hey </button>
  </form>

</template>

<script>

  export default {
    name: 'facilityNameForm',
  };

</script>

<style lang="stylus" scoped></style>


Use pseudo elements to prevent copying in Safari

<template>

        <div class="execution-log__content" v-bind:class="[`execution-log__content--level-${$options.entry.level.toLowerCase()}`,
            {
                'execution-log__content--html': $options.entry.logHtml
            }]"
        >

            {{$options.entry.log}}

</template>

<template>

                {{timestamps ? $options.entry.time : ''}}

                {{command ? $options.entry.stepLabel : ''}}

<div class="execution-log__content" v-bind:class="[`execution-log__content--level-${$options.entry.level.toLowerCase()}`,
            {
                'execution-log__content--html': $options.entry.logHtml
            }]"
            > {{$options.entry.node}}{{$options.entry.log}}

</template>


Fix progress and footer display.

Fix progress and footer display.

<template>

    <router-link
      :to="link"
      class="card"
      :class="[
        { 'mobile-card': isMobile },
        $computedClass({ ':focus': $coreOutline })
      ]"
      :style="{ backgroundColor: $themeTokens.surface }"
    >

        <CardThumbnail
          :kind="content.kind"
          v-bind="{ thumbnail, isMobile }"
          :activityLength="content.duration"
        />

          {{ bookmarkCreated }}

        <ProgressBar v-if="!isMobile" :contentNode="content" />

          <LearningActivityLabel :contentNode="content" class="learning-activity-label" />

          <TextTruncator
            :text="content.title"
            :maxHeight="maxTitleHeight"
          />

          <TextTruncator
            :text="content.description"
            :maxHeight="maxDescriptionHeight"
          />

 {{ coreString(displayCategoryAndLevelMetadata) }}

        <KButton
          v-if="!isMobile && isLibraryPage && content.copies_count && (content.copies_count > 0)"
          appearance="basic-link"
          class="copies"
          :style="{ color: $themeTokens.text }"
          :text="coreString('copies', { num: content.copies_count })"
          @click.prevent="$emit('openCopiesModal', contentId)"
        />

    </router-link>
    <KFixedGrid :numCols="isMobile ? 8 : 2" class="footer">
      <KFixedGridItem v-if="isMobile" span="6">

          {{ bookmarkCreated }}

        <ProgressBar :contentNode="content" />
      </KFixedGridItem>
      <KFixedGridItem span="2" alignment="right">
        <KIconButton
          v-for="(value, key) in footerIcons"
          :key="key"
          :icon="key"
          size="mini"
          :color="$themePalette.grey.v_400"
          :ariaLabel="coreString(value)"
          :tooltip="coreString(value)"
          @click="$emit(value)"
        />
      </KFixedGridItem>
    </KFixedGrid>

</template>

<template>

    <router-link
      :to="link"
      class="card"
      :class="[
        { 'mobile-card': isMobile },
        $computedClass({ ':focus': $coreOutline })
      ]"
      :style="{ backgroundColor: $themeTokens.surface }"
    >

        <CardThumbnail
          :kind="content.kind"
          v-bind="{ thumbnail, isMobile }"
          :activityLength="content.duration"
        />

          {{ bookmarkCreated }}

          <LearningActivityLabel :contentNode="content" class="learning-activity-label" />

          <TextTruncator
            :text="content.title"
            :maxHeight="maxTitleHeight"
          />

          <TextTruncator
            :text="content.description"
            :maxHeight="maxDescriptionHeight"
          />

 {{ coreString(displayCategoryAndLevelMetadata) }}

        <KButton
          v-if="!isMobile && isLibraryPage && content.copies_count && (content.copies_count > 0)"
          appearance="basic-link"
          class="copies"
          :style="{ color: $themeTokens.text }"
          :text="coreString('copies', { num: content.copies_count })"
          @click.prevent="$emit('openCopiesModal', contentId)"
        />

    </router-link>

        {{ bookmarkCreated }}

      <ProgressBar v-else :contentNode="content" />

        <KIconButton
          v-for="(value, key) in footerIcons"
          :key="key"
          :icon="key"
          size="mini"
          :color="$themePalette.grey.v_400"
          :ariaLabel="coreString(value)"
          :tooltip="coreString(value)"
          @click="$emit(value)"
        />

</template>


Only delete orphaned Series

Only delete orphaned Series

Refs #78.

<template>
	<v-container fluid>
		<portal v-if="!loading" to="appBarAddons">
			<FavouriteButton :model-id="series.id" type="Series" />
		</portal>

		<AppTasks v-if="!loading" v-model="appTasksMenu">
			<AppTask
				:label="$t('quickTasks.edit.series')"
				:to="`/series/${series.id}/edit`"
				icon="mdi-animation dem-overlay-edit"
			/>
			<AppTask
				v-if="albumsLoaded && albums.length <= 0 && derivativeCount <= 0"
				:label="$t('quickTasks.delete.series')"
				:confirm="$t('quickTasks.delete.series.confirm')"
				icon="mdi-animation dem-overlay-delete"
				@cancel="appTasksMenu = false"
				@confirm="deleteSeries"
			/>
			<AppTask
				v-if="hasAlbumsOutsideReadingList"
				:label="$t('quickTasks.add.series.to.readingList')"
				icon="mdi-library"
				@click="addSeriesToReadingList"
			/>
			<AppTask
				:label="$t('quickTasks.add.derivative.to.series')"
				:to="{ name: 'DerivativeAdd', query: derivativeQuery}"
				icon="mdi-image-frame dem-overlay-add"
			/>
		</AppTasks>

		<SectionCard :loading="loading" :title="series.identifyingName">
			<FieldValue v-if="series.website" :label="$t('field.Series.website')">
				{{ series.website }}
			</FieldValue>

			<FieldValue :label="$t('field.Series.completed.view')">
				<template v-if="series.completed">
					{{ $t('field.Series.completed.value.true') }}
				</template>
				<template v-if="!series.completed">
					{{ $t('field.Series.completed.value.false') }}
				</template>
			</FieldValue>

			<FieldValue v-if="series.location" :label="$t('field.Series.location')">
				{{ series.location }}
			</FieldValue>

			<FieldValue v-if="series.relatedSeries" :label="$t('field.Series.relatedSeries')">
				<ModelLink :model="series.relatedSeries" view="SeriesView" />
			</FieldValue>

				<FieldValue v-if="series.biography" :label="$t('field.Series.summary')">

				</FieldValue>
				<FieldValue v-if="series.comment" :label="$t('field.Series.comment')">

				</FieldValue>

		</SectionCard>

		<SectionCard v-if="!loading && series.albumIds" class="c-SectionCard--tabbed">
			<v-tabs background-color="primary" dark grow>
				<v-tab>
					<v-icon left>
						mdi-book-open-variant
					</v-icon>
					{{ $t('field.Series.albums') }}
				</v-tab>
				<v-tab :disabled="derivativeCount <= 0" @change="loadDerivatives">
					<v-icon left>
						mdi-image-frame
					</v-icon>
					{{ $tc('field.Series.derivatives', derivativeCount) }}
				</v-tab>

				<v-tab-item class="dem-tab">

						<FieldValue :label="$t('field.Series.albumCount')">
							<template v-if="albumCount === ownedAlbumCount">
								{{ $t('field.Series.albumCount.count.full', [albumCount]) }}
							</template>
							<template v-else>
								{{ $t('field.Series.albumCount.count.partial', [ownedAlbumCount, albumCount]) }}
							</template>
						</FieldValue>

						<FieldValue v-if="allWriters.length" :label="$tc('field.Album.writers', allWriters.length)">
							<ModelLink :model="allWriters" view="AuthorView" />
						</FieldValue>
						<FieldValue v-if="allArtists.length" :label="$tc('field.Album.artists', allArtists.length)">
							<ModelLink :model="allArtists" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="allColorists.length" :label="$tc('field.Album.colorists', allColorists.length)"
						>
							<ModelLink :model="allColorists" view="AuthorView" />
						</FieldValue>
						<FieldValue v-if="allInkers.length" :label="$tc('field.Album.inkers', allInkers.length)">
							<ModelLink :model="allInkers" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="allTranslators.length" :label="$tc('field.Album.translators', allTranslators.length)"
						>
							<ModelLink :model="allTranslators" view="AuthorView" />
						</FieldValue>

						<FieldValue v-if="allTags.length" :label="$tc('field.Album.tags', allTags.length)">
							<TagLink :model="allTags" />
						</FieldValue>

					<v-switch
						v-if="albumCount !== ownedAlbumCount" v-model="showWishlist"
						:label="$t('page.Series.showWishlist')" prepend-icon="mdi-gift"
					/>
					<v-row>
						<v-col
							v-for="albumId in filteredIds" :key="albumId"
							cols="12" md="6" lg="4"
						>
							<AlbumCard :album="albums[albumId]" :loading="albums[albumId].loading" />
						</v-col>
					</v-row>
				</v-tab-item>

				<v-tab-item class="dem-tab">
					<div v-if="derivatives.length <= 0" class="text-center">
						<v-progress-circular indeterminate color="primary" size="64" />

					<GalleryIndex :items="derivatives" image-path="mainImage" bordered>
						<template v-slot:default="slotProps">
							<router-link :to="`/derivatives/${slotProps.item.id}/view`">

									{{ slotProps.item.album.title }}

									{{ slotProps.item.source.identifyingName }}

							</router-link>
						</template>
					</GalleryIndex>
				</v-tab-item>
			</v-tabs>
		</SectionCard>
	</v-container>
</template>

<template>
	<v-container fluid>
		<portal v-if="!loading" to="appBarAddons">
			<FavouriteButton :model-id="series.id" type="Series" />
		</portal>

		<AppTasks v-if="!loading" v-model="appTasksMenu">
			<AppTask
				:label="$t('quickTasks.edit.series')"
				:to="`/series/${series.id}/edit`"
				icon="mdi-animation dem-overlay-edit"
			/>
			<AppTask
				:label="$t('quickTasks.delete.series')"
				:confirm="$t('quickTasks.delete.series.confirm')"
				icon="mdi-animation dem-overlay-delete"
				@cancel="appTasksMenu = false"
				@confirm="deleteSeries"
			/>
			<AppTask
				v-if="hasAlbumsOutsideReadingList"
				:label="$t('quickTasks.add.series.to.readingList')"
				icon="mdi-library"
				@click="addSeriesToReadingList"
			/>
			<AppTask
				:label="$t('quickTasks.add.derivative.to.series')"
				:to="{ name: 'DerivativeAdd', query: derivativeQuery}"
				icon="mdi-image-frame dem-overlay-add"
			/>
		</AppTasks>

		<SectionCard :loading="loading" :title="series.identifyingName">
			<FieldValue v-if="series.website" :label="$t('field.Series.website')">
				{{ series.website }}
			</FieldValue>

			<FieldValue :label="$t('field.Series.completed.view')">
				<template v-if="series.completed">
					{{ $t('field.Series.completed.value.true') }}
				</template>
				<template v-if="!series.completed">
					{{ $t('field.Series.completed.value.false') }}
				</template>
			</FieldValue>

			<FieldValue v-if="series.location" :label="$t('field.Series.location')">
				{{ series.location }}
			</FieldValue>

			<FieldValue v-if="series.relatedSeries" :label="$t('field.Series.relatedSeries')">
				<ModelLink :model="series.relatedSeries" view="SeriesView" />
			</FieldValue>

				<FieldValue v-if="series.biography" :label="$t('field.Series.summary')">

				</FieldValue>
				<FieldValue v-if="series.comment" :label="$t('field.Series.comment')">

				</FieldValue>

		</SectionCard>

		<SectionCard v-if="!loading && series.albumIds" class="c-SectionCard--tabbed">
			<v-tabs background-color="primary" dark grow>
				<v-tab>
					<v-icon left>
						mdi-book-open-variant
					</v-icon>
					{{ $t('field.Series.albums') }}
				</v-tab>
				<v-tab :disabled="derivativeCount <= 0" @change="loadDerivatives">
					<v-icon left>
						mdi-image-frame
					</v-icon>
					{{ $tc('field.Series.derivatives', derivativeCount) }}
				</v-tab>

				<v-tab-item class="dem-tab">

						<FieldValue :label="$t('field.Series.albumCount')">
							<template v-if="albumCount === ownedAlbumCount">
								{{ $t('field.Series.albumCount.count.full', [albumCount]) }}
							</template>
							<template v-else>
								{{ $t('field.Series.albumCount.count.partial', [ownedAlbumCount, albumCount]) }}
							</template>
						</FieldValue>

						<FieldValue v-if="allWriters.length" :label="$tc('field.Album.writers', allWriters.length)">
							<ModelLink :model="allWriters" view="AuthorView" />
						</FieldValue>
						<FieldValue v-if="allArtists.length" :label="$tc('field.Album.artists', allArtists.length)">
							<ModelLink :model="allArtists" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="allColorists.length" :label="$tc('field.Album.colorists', allColorists.length)"
						>
							<ModelLink :model="allColorists" view="AuthorView" />
						</FieldValue>
						<FieldValue v-if="allInkers.length" :label="$tc('field.Album.inkers', allInkers.length)">
							<ModelLink :model="allInkers" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="allTranslators.length" :label="$tc('field.Album.translators', allTranslators.length)"
						>
							<ModelLink :model="allTranslators" view="AuthorView" />
						</FieldValue>

						<FieldValue v-if="allTags.length" :label="$tc('field.Album.tags', allTags.length)">
							<TagLink :model="allTags" />
						</FieldValue>

					<v-switch
						v-if="albumCount !== ownedAlbumCount" v-model="showWishlist"
						:label="$t('page.Series.showWishlist')" prepend-icon="mdi-gift"
					/>
					<v-row>
						<v-col
							v-for="albumId in filteredIds" :key="albumId"
							cols="12" md="6" lg="4"
						>
							<AlbumCard :album="albums[albumId]" :loading="albums[albumId].loading" />
						</v-col>
					</v-row>
				</v-tab-item>

				<v-tab-item class="dem-tab">
					<div v-if="derivatives.length <= 0" class="text-center">
						<v-progress-circular indeterminate color="primary" size="64" />

					<GalleryIndex :items="derivatives" image-path="mainImage" bordered>
						<template v-slot:default="slotProps">
							<router-link :to="`/derivatives/${slotProps.item.id}/view`">

									{{ slotProps.item.album.title }}

									{{ slotProps.item.source.identifyingName }}

							</router-link>
						</template>
					</GalleryIndex>
				</v-tab-item>
			</v-tabs>
		</SectionCard>
	</v-container>
</template>


:art:

:art:

<template>
<ui-card>
	<template #title><fa icon="cloud"/> {{ $t('@.drive') }}</template>

{{ $t('max') }}: {{ capacity | bytes }} {{ $t('in-use') }}: {{ usage | bytes }}

		{{ $t('stats') }}

		{{ $t('default-upload-folder') }}
		<ui-input v-model="uploadFolderName" readonly>{{ $t('default-upload-folder-name') }}</ui-input>
		<ui-button @click="chooseUploadFolder()">{{ $t('change-default-upload-folder') }}</ui-button>

</ui-card>
</template>


Use core-table in permissions page

Use core-table in permissions page

<template>

      {{ $tr('noUsersMatching', { searchFilter }) }}

    <core-table v-else>

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

            <permissions-icon
              v-if="!!getPermissionType(user.id)"
              :permissionType="getPermissionType(user.id)"
            />

            {{ user.full_name }}
             ({{ $tr('you') }})

            {{ user.username }}

            <k-button
              @click="goToUserPermissionsPage(user.id)"
              appearance="flat-button"
              :text="permissionsButtonText(user.username)"
            />

    </core-table>

</template>

<template>

      {{ $tr('noUsersMatching', { searchFilter }) }}

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

            <permissions-icon
              v-if="!!getPermissionType(user.id)"
              :permissionType="getPermissionType(user.id)"
            />

            {{ user.full_name }}
             ({{ $tr('you') }})

            {{ user.username }}

            <k-button
              @click="goToUserPermissionsPage(user.id)"
              appearance="flat-button"
              :text="permissionsButtonText(user.username)"
            />

</template>


Fixed 412 issue on channel set modal loading

<template>

  <VDialog
    ref="dialog"
    :value="$route.params.channelSetId == channelSetId"
    attach="body"
    fullscreen
    scrollable
    transition="dialog-bottom-transition"
  >
    <VCard style="overflow-y: auto;">
      <VWindow v-model="step">
        <VWindowItem :value="1">
          <VToolbar card prominent dark color="primary">
            <VBtn icon data-test="close" @click="cancelChanges">
              <Icon>clear</Icon>
            </VBtn>
            <MessageDialog
              v-model="showUnsavedDialog"
              :header="$tr('unsavedChangesHeader')"
              :text="$tr('unsavedChangesText')"
            >
              <template #buttons="{close}">
                <VSpacer />
                <VBtn flat data-test="confirm-cancel" @click="confirmCancel">
                  {{ $tr('closeButton') }}
                </VBtn>
                <VBtn color="primary" @click="save">
                  {{ $tr('saveButton') }}
                </VBtn>
              </template>
            </MessageDialog>
            <VToolbarTitle>
              {{ headerText }}
            </VToolbarTitle>
            <VSpacer />
            <VBtn flat data-test="save" @click="save">
              {{ saveText }}
            </VBtn>
          </VToolbar>
          <VContainer>
            <VLayout row justify-center>
              <VFlex md12 lg10 xl8>
                <VForm ref="channelsetform">
                  <VTextField
                    v-model="name"
                    :rules="[nameValid]"
                    :label="$tr('titleLabel')"
                    maxlength="200"
                    counter
                    outline
                  />
                </VForm>

{{ $tr('tokenPrompt') }}

                    {{ $tr('token') }}

                  <CopyToken
                    :token="channelSet.secret_token"
                    style="max-width: max-content;"
                  />

                  {{ $tr('channels') }}

                  {{ $tr('channelCountText', {'channelCount': channels.length}) }}

                <VCardText v-if="loadingChannels">
                  {{ $tr('loading') }}
                </VCardText>

                  <VBtn color="primary" data-test="select" @click="step ++">
                    {{ $tr('selectChannelsHeader') }}
                  </VBtn>
                  <VCard v-for="channelId in channels" :key="channelId" flat>
                    <ChannelItem :channelId="channelId">
                      <VBtn
                        flat
                        class="ma-0"
                        color="primary"
                        data-test="remove"
                        @click="removeChannel(channelId)"
                      >
                        {{ $tr('removeText') }}
                      </VBtn>
                    </ChannelItem>
                  </VCard>

              </VFlex>
            </VLayout>
          </VContainer>
        </VWindowItem>
        <VWindowItem :value="2" lazy>
          <VToolbar card prominent dark color="primary" class="notranslate">
            <VBtn icon @click="step --">
              <VIcon class="notranslate">
                arrow_back
              </VIcon>
            </VBtn>
            <VToolbarTitle>
              {{ $tr('selectChannelsHeader') }}
            </VToolbarTitle>
          </VToolbar>
          <VContainer fill-height>
            <VLayout row justify-center>
              <VFlex md12 lg10 xl8>

                  {{ $tr('selectChannelsHeader') }}

{{ $tr('publishedChannelsOnlyText') }}

                <VContainer>
                  <VTabs showArrows slider-color="primary">
                    <VTab
                      v-for="listType in lists"
                      :key="listType.id"
                    >
                      {{ $tr(listType) }}
                    </VTab>
                    <VTabItem
                      v-for="listType in lists"
                      :key="listType.id"
                      lazy
                    >
                      <ChannelSelectionList v-model="channels" :listType="listType" />
                    </VTabItem>
                  </VTabs>
                </VContainer>
              </VFlex>
            </VLayout>
          </VContainer>
          <BottomToolBar color="white" flat>
            <VSpacer />

              {{ $tr('channelSelectedCountText', {'channelCount': channels.length}) }}

            <VBtn
              color="primary"
              data-test="finish"
              @click="step --"
            >
              {{ $tr('finish') }}
            </VBtn>
          </BottomToolBar>
        </VWindowItem>
      </VWindow>
    </VCard>
  </VDialog>

</template>


fix:VaModal fixes (#518)

fix:VaModal fixes (#518)

* fix: VaModal fix overlay styles

* chore: formatting

<template>
  <transition name="va-modal__overlay__transition" appear :duration="withoutTransitions ? 0 : 200">

      <transition name="va-modal__transition" appear :duration="withoutTransitions ? 0 : 500">

{{title}}

              <slot name="header"/>

{{message}}

              <slot/>

              <va-button v-if="cancelText" color="gray" flat @click="cancel">
                {{cancelText}}
              </va-button>
              <va-button @click="ok">{{okText}}</va-button>

              <slot name="actions"/>

      </transition>

  </transition>
</template>


Added no-ssr directive.

Added no-ssr directive.

<template>

        <no-ssr>

                        {{$t('common:cookiePrivacyInfo.info1')}}
                        <nuxt-link :to="{name: 'privacy'}">{{$t('common:cookiePrivacyInfo.infoLinkPrivacy')}}
                        </nuxt-link>
                         {{$t('common:cookiePrivacyInfo.info2')}}

                    <v-btn color="secondary" @click="acceptCookies">
                        {{$t('common:button.ok')}}
                    </v-btn>

        </no-ssr>

</template>

<template>

                {{$t('common:cookiePrivacyInfo.info1')}}
                <nuxt-link :to="{name: 'privacy'}">{{$t('common:cookiePrivacyInfo.infoLinkPrivacy')}}</nuxt-link>
                 {{$t('common:cookiePrivacyInfo.info2')}}

            <v-btn color="secondary" @click="acceptCookies">
                {{$t('common:button.ok')}}
            </v-btn>

</template>


If it is a token transfer, it shows the recipient and the contract address

<template lang="html">

          {{ direction === 'from' ? '-' : '+' }}
          {{ tokenTransferVal !== '' ? tokenTransferVal : converter(value) }}

        {{ tokenSymbol !== '' ? tokenSymbol : currency.toUpperCase() }}

{{ direction | capitalize }} Address

{{ checksumAddress }}

Via contract

{{ tokenChecksumAddress }}

</template>

<template lang="html">

          {{ direction === 'from' ? '-' : '+' }}
          {{ tokenTransferVal !== '' ? tokenTransferVal : converter(value) }}

        {{ tokenSymbol !== '' ? tokenSymbol : currency.toUpperCase() }}

{{ direction | capitalize }} Address

{{ checksumAddress }}

</template>


Radio: fix console error of RadioGroup

Radio: fix console error of RadioGroup

<template>
  <div
    class="el-radio-group"
    role="radiogroup"
    @keydown="handleKeydown"
  >
    <slot></slot>
  </div>
</template>
<script>
  import Emitter from 'element-ui/src/mixins/emitter';

  const keyCode = Object.freeze({
    LEFT: 37,
    UP: 38,
    RIGHT: 39,
    DOWN: 40
  });
  export default {
    name: 'ElRadioGroup',

    componentName: 'ElRadioGroup',

    inject: {
      elFormItem: {
        default: ''
      }
    },

    mixins: [Emitter],

    props: {
      value: {},
      size: String,
      fill: String,
      textColor: String,
      disabled: Boolean
    },

    computed: {
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      radioGroupSize() {
        return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
      }
    },

    created() {
      this.$on('handleChange', value => {
        this.$emit('change', value);
      });
    },
    mounted() {
      // 当radioGroup没有默认选项时，第一个可以选中Tab导航
      const radios = this.$el.querySelectorAll('[type=radio]');
      const firstLabel = this.$el.querySelectorAll('[role=radio]')[0];
      if (![].some.call(radios, radio => radio.checked) && firstLabel) {
        firstLabel.tabIndex = 0;
      }
    },
    methods: {
      handleKeydown(e) { // 左右上下按键 可以在radio组内切换不同选项
        const target = e.target;
        const className = target.nodeName === 'INPUT' ? '[type=radio]' : '[role=radio]';
        const radios = this.$el.querySelectorAll(className);
        const length = radios.length;
        const index = [].indexOf.call(radios, target);
        const roleRadios = this.$el.querySelectorAll('[role=radio]');
        switch (e.keyCode) {
          case keyCode.LEFT:
          case keyCode.UP:
            e.stopPropagation();
            e.preventDefault();
            if (index === 0) {
              roleRadios[length - 1].click();
            } else {
              roleRadios[index - 1].click();
            }
            break;
          case keyCode.RIGHT:
          case keyCode.DOWN:
            if (index === (length - 1)) {
              e.stopPropagation();
              e.preventDefault();
              roleRadios[0].click();
            } else {
              roleRadios[index + 1].click();
            }
            break;
          default:
            break;
        }
      }
    },
    watch: {
      value(value) {
        this.dispatch('ElFormItem', 'el.form.change', [this.value]);
      }
    }
  };
</script>

<template>
  <div
    class="el-radio-group"
    role="radiogroup"
    @keydown="handleKeydown"
  >
    <slot></slot>
  </div>
</template>
<script>
  import Emitter from 'element-ui/src/mixins/emitter';

  const keyCode = Object.freeze({
    LEFT: 37,
    UP: 38,
    RIGHT: 39,
    DOWN: 40
  });
  export default {
    name: 'ElRadioGroup',

    componentName: 'ElRadioGroup',

    inject: {
      elFormItem: {
        default: ''
      }
    },

    mixins: [Emitter],

    props: {
      value: {},
      size: String,
      fill: String,
      textColor: String,
      disabled: Boolean
    },

    computed: {
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      radioGroupSize() {
        return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
      }
    },

    created() {
      this.$on('handleChange', value => {
        this.$emit('change', value);
      });
    },
    mounted() {
      // 当radioGroup没有默认选项时，第一个可以选中Tab导航
      let radios = this.$el.querySelectorAll('[type=radio]');
      if (![].some.call(radios, radio => radio.checked)) {
        this.$el.querySelectorAll('[role=radio]')[0].tabIndex = 0;
      }
    },
    methods: {
      handleKeydown(e) { // 左右上下按键 可以在radio组内切换不同选项
        const target = e.target;
        const className = target.nodeName === 'INPUT' ? '[type=radio]' : '[role=radio]';
        const radios = this.$el.querySelectorAll(className);
        const length = radios.length;
        const index = [].indexOf.call(radios, target);
        const roleRadios = this.$el.querySelectorAll('[role=radio]');
        switch (e.keyCode) {
          case keyCode.LEFT:
          case keyCode.UP:
            e.stopPropagation();
            e.preventDefault();
            if (index === 0) {
              roleRadios[length - 1].click();
            } else {
              roleRadios[index - 1].click();
            }
            break;
          case keyCode.RIGHT:
          case keyCode.DOWN:
            if (index === (length - 1)) {
              e.stopPropagation();
              e.preventDefault();
              roleRadios[0].click();
            } else {
              roleRadios[index + 1].click();
            }
            break;
          default:
            break;
        }
      }
    },
    watch: {
      value(value) {
        this.dispatch('ElFormItem', 'el.form.change', [this.value]);
      }
    }
  };
</script>


如果不是本班级同学 显示其班级

如果不是本班级同学 显示其班级

<template>

        已入住{{dormNum}}的同学

            暂无人选择该宿舍

                    {{student.student_name}}
                    {{student.diff_class_name}}

</template>

<template>

        已入住{{dormNum}}的同学

            暂无人选择该宿舍

                    {{student.student_name}}
                    15级网工2班

</template>


统一顶部选项卡dom结构

统一顶部选项卡dom结构

<template>
    <view class="uni-tab-bar">
        <scroll-view id="tab-bar" class="uni-swiper-tab" scroll-x :scroll-left="scrollLeft">
            <view v-for="(tab,index) in tabBars" :key="tab.id" :class="['swiper-tab-list',tabIndex==index ? 'active' : '']"
                :id="tab.id" :data-current="index" @tap="tapTab">{{tab.name}}</view>
        </scroll-view>
        <swiper :current="tabIndex" class="swiper-box" duration="300" @change="changeTab">
            <swiper-item v-for="(tab,index1) in newsitems" :key="index1">
                <scroll-view class="list" scroll-y @scrolltolower="loadMore(index1)">
                    <block v-for="(newsitem,index2) in tab.data" :key="index2">
                        <media-list :data="newsitem" @close="close(index1,index2)" @click="goDetail(newsitem)"></media-list>
                    </block>
					<view class="uni-tab-bar-loading">
						<load-more :loadingType="tab.loadingType" :contentText="loadingText"></load-more>
					</view>
                </scroll-view>
            </swiper-item>
        </swiper>
    </view>
</template>
<script>
    import mediaList from '@/components/tab-nvue/mediaList.vue';
	import loadMore from '@/components/load-more.vue';
    export default {
        components: {
            mediaList,
			loadMore
        },
        data() {
            return {
				loadingText: {
					contentdown: "上拉显示更多",
					contentrefresh: "正在加载...",
					contentnomore: "没有更多数据了"
				},
                scrollLeft: 0,
                isClickChange: false,
                tabIndex: 0,
                newsitems: [],
                data0: {
                    "datetime": "40分钟前",
                    "article_type": 0,
                    "title": "uni-app行业峰会频频亮相，开发者反响热烈!",
                    "source": "DCloud",
                    "comment_count": 639
                },
                data1: {
                    "datetime": "一天前",
                    "article_type": 1,
                    "title": "DCloud完成B2轮融资，uni-app震撼发布!",
                    "image_url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/shuijiao.jpg?imageView2/3/w/200/h/100/q/90",
                    "source": "DCloud",
                    "comment_count": 11395
                },
                data2: {
                    "datetime": "一天前",
                    "article_type": 2,
                    "title": "中国技术界小奇迹：HBuilder开发者突破200万",
                    "image_url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/muwu.jpg?imageView2/3/w/200/h/100/q/90",
                    "source": "DCloud",
                    "comment_count": 11395
                },
                data4: {
                    "datetime": "2小时前",
                    "article_type": 4,
                    "title": "uni-app 支持原生小程序自定义组件，更开放、更自由",
                    "image_url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/cbd.jpg?imageView2/3/w/200/h/100/q/90",
                    "source": "DCloud",
                    "comment_count": 69
                },
                data3: {
                    "article_type": 3,
                    "image_list": [{
                        "url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/cbd.jpg?imageView2/3/w/200/h/100/q/90",
                        "width": 563,
                        "height": 316
                    }, {
                        "url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/muwu.jpg?imageView2/3/w/200/h/100/q/90",
                        "width": 641,
                        "height": 360
                    }, {
                        "url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/shuijiao.jpg?imageView2/3/w/200/h/100/q/90",
                        "width": 640,
                        "height": 360
                    }],
                    "datetime": "5分钟前",
                    "title": "uni-app 支持使用 npm 安装第三方包，生态更趋丰富",
                    "source": "DCloud",
                    "comment_count": 11
                },
                tabBars: [{
                    name: '关注',
                    id: 'guanzhu'
                }, {
                    name: '推荐',
                    id: 'tuijian'
                }, {
                    name: '体育',
                    id: 'tiyu'
                }, {
                    name: '热点',
                    id: 'redian'
                }, {
                    name: '财经',
                    id: 'caijing'
                }, {
                    name: '娱乐',
                    id: 'yule'
                }, {
                    name: '军事',
                    id: 'junshi'
                }, {
                    name: '历史',
                    id: 'lishi'
                }, {
                    name: '本地',
                    id: 'bendi'
                }]
            }
        },
        onLoad: function() {
            this.newsitems = this.randomfn()
        },
        methods: {
            goDetail(e) {
                uni.navigateTo({
                    url: '/pages/template/tabbar/detail/detail?data=' + e.title
                })
            },
            close(index1, index2) {
                uni.showModal({
                    content: '是否删除本条信息？',
                    success: (res) => {
                        if (res.confirm) {
                            this.newsitems[index1].data.splice(index2, 1);
                        }
                    }
                })
            },
            loadMore(e) {
				this.newsitems[e].loadingType = 1;
            	setTimeout(() => {
            		this.addData(e);
            	}, 1200);
            },
            addData(e) {
            	if (this.newsitems[e].data.length > 30) {
					this.newsitems[e].loadingType = 2;
            		return;
            	}
            	for (let i = 1; i <= 10; i++) {
            		this.newsitems[e].data.push(this['data' + Math.floor(Math.random() * 5)]);
            	}
				this.newsitems[e].loadingType = 1;
            },
            async changeTab(e) {
                let index = e.target.current;
                if (this.isClickChange) {
                    this.tabIndex = index;
                    this.isClickChange = false;
                    return;
                }
                let tabBar = await this.getElSize("tab-bar"),
                    tabBarScrollLeft = tabBar.scrollLeft;
                let width = 0;

                for (let i = 0; i < index; i++) {
                    let result = await this.getElSize(this.tabBars[i].id);
                    width += result.width;
                }
                let winWidth = uni.getSystemInfoSync().windowWidth,
                    nowElement = await this.getElSize(this.tabBars[index].id),
                    nowWidth = nowElement.width;
                if (width + nowWidth - tabBarScrollLeft > winWidth) {
                    this.scrollLeft = width + nowWidth - winWidth;
                }
                if (width < tabBarScrollLeft) {
                    this.scrollLeft = width;
                }
                this.isClickChange = false;
                this.tabIndex = index; //一旦访问data就会出问题
            },
            getElSize(id) { //得到元素的size
                return new Promise((res, rej) => {
                    uni.createSelectorQuery().select("#" + id).fields({
                        size: true,
                        scrollOffset: true
                    }, (data) => {
                        res(data);
                    }).exec();
                })
            },
            async tapTab(e) { //点击tab-bar
                if (this.tabIndex === e.target.dataset.current) {
                    return false;
                } else {
                    let tabBar = await this.getElSize("tab-bar"),
                        tabBarScrollLeft = tabBar.scrollLeft; //点击的时候记录并设置scrollLeft
                    this.scrollLeft = tabBarScrollLeft;
                    this.isClickChange = true;
                    this.tabIndex = e.target.dataset.current
                }
            },
            randomfn() {
                let ary = [];
                for (let i = 0, length = this.tabBars.length; i < length; i++) {
                    let aryItem = {
						loadingType : 0,
                        data: []
                    };
                    for (let j = 1; j <= 10; j++) {
                        aryItem.data.push(this['data' + Math.floor(Math.random() * 5)]);
                    }
                    ary.push(aryItem);
                }
                return ary;
            }
        }
    }
</script>

<style>

</style>

<template>
    <view class="index">
        <scroll-view id="tab-bar" class="swiper-tab" scroll-x :scroll-left="scrollLeft">
            <view v-for="(tab,index) in tabBars" :key="tab.id" :class="['swiper-tab-list',tabIndex==index ? 'active' : '']"
                :id="tab.id" :data-current="index" @tap="tapTab">{{tab.name}}</view>
        </scroll-view>
        <swiper :current="tabIndex" class="swiper-box" duration="300" @change="changeTab">
            <swiper-item v-for="(tab,index1) in newsitems" :key="index1">
                <scroll-view class="list" scroll-y @scrolltolower="loadMore(index1)">
                    <block v-for="(newsitem,index2) in tab.data" :key="index2">
                        <media-list :data="newsitem" @close="close(index1,index2)" @click="goDetail(newsitem)"></media-list>
                    </block>
                    <view class="loadmore">
                        <text class="loadmore-text">{{tab.loadingText}}</text>
                    </view>
                </scroll-view>
            </swiper-item>
        </swiper>
    </view>
</template>
<script>
    import mediaList from '@/components/tab-nvue/mediaList.vue'
    export default {
        components: {
            mediaList
        },
        data() {
            return {
                scrollLeft: 0,
                isClickChange: false,
                tabIndex: 0,
                newsitems: [],
                data0: {
                    "datetime": "40分钟前",
                    "article_type": 0,
                    "title": "uni-app行业峰会频频亮相，开发者反响热烈!",
                    "source": "DCloud",
                    "comment_count": 639
                },
                data1: {
                    "datetime": "一天前",
                    "article_type": 1,
                    "title": "DCloud完成B2轮融资，uni-app震撼发布!",
                    "image_url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/shuijiao.jpg?imageView2/3/w/200/h/100/q/90",
                    "source": "DCloud",
                    "comment_count": 11395
                },
                data2: {
                    "datetime": "一天前",
                    "article_type": 2,
                    "title": "中国技术界小奇迹：HBuilder开发者突破200万",
                    "image_url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/muwu.jpg?imageView2/3/w/200/h/100/q/90",
                    "source": "DCloud",
                    "comment_count": 11395
                },
                data4: {
                    "datetime": "2小时前",
                    "article_type": 4,
                    "title": "uni-app 支持原生小程序自定义组件，更开放、更自由",
                    "image_url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/cbd.jpg?imageView2/3/w/200/h/100/q/90",
                    "source": "DCloud",
                    "comment_count": 69
                },
                data3: {
                    "article_type": 3,
                    "image_list": [{
                        "url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/cbd.jpg?imageView2/3/w/200/h/100/q/90",
                        "width": 563,
                        "height": 316
                    }, {
                        "url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/muwu.jpg?imageView2/3/w/200/h/100/q/90",
                        "width": 641,
                        "height": 360
                    }, {
                        "url": "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/shuijiao.jpg?imageView2/3/w/200/h/100/q/90",
                        "width": 640,
                        "height": 360
                    }],
                    "datetime": "5分钟前",
                    "title": "uni-app 支持使用 npm 安装第三方包，生态更趋丰富",
                    "source": "DCloud",
                    "comment_count": 11
                },
                tabBars: [{
                    name: '关注',
                    id: 'guanzhu'
                }, {
                    name: '推荐',
                    id: 'tuijian'
                }, {
                    name: '体育',
                    id: 'tiyu'
                }, {
                    name: '热点',
                    id: 'redian'
                }, {
                    name: '财经',
                    id: 'caijing'
                }, {
                    name: '娱乐',
                    id: 'yule'
                }, {
                    name: '军事',
                    id: 'junshi'
                }, {
                    name: '历史',
                    id: 'lishi'
                }, {
                    name: '本地',
                    id: 'bendi'
                }]
            }
        },
        onLoad: function() {
            this.newsitems = this.randomfn()
        },
        methods: {
            goDetail(e) {
                uni.navigateTo({
                    url: '/pages/template/tabbar/detail/detail?data=' + e.title
                })
            },
            close(index1, index2) {
                uni.showModal({
                    content: '是否删除本条信息？',
                    success: (res) => {
                        if (res.confirm) {
                            this.newsitems[index1].data.splice(index2, 1);
                        }
                    }
                })
            },
            loadMore(e) {
            	setTimeout(() => {
            		this.addData(e);
            	}, 1000);
            },
            addData(e) {
            	console.log("加载更多...");
            	if (this.newsitems[e].data.length > 30) {
            		this.newsitems[e].loadingText = '没有更多了';
            		return;
            	}
            	for (let i = 1; i <= 10; i++) {
            		this.newsitems[e].data.push(this['data' + Math.floor(Math.random() * 5)]);
            	}
            },
            async changeTab(e) {
                let index = e.target.current;
                if (this.isClickChange) {
                    this.tabIndex = index;
                    this.isClickChange = false;
                    return;
                }
                let tabBar = await this.getElSize("tab-bar"),
                    tabBarScrollLeft = tabBar.scrollLeft;
                let width = 0;

                for (let i = 0; i < index; i++) {
                    let result = await this.getElSize(this.tabBars[i].id);
                    width += result.width;
                }
                let winWidth = uni.getSystemInfoSync().windowWidth,
                    nowElement = await this.getElSize(this.tabBars[index].id),
                    nowWidth = nowElement.width;
                if (width + nowWidth - tabBarScrollLeft > winWidth) {
                    this.scrollLeft = width + nowWidth - winWidth;
                }
                if (width < tabBarScrollLeft) {
                    this.scrollLeft = width;
                }
                this.isClickChange = false;
                this.tabIndex = index; //一旦访问data就会出问题
            },
            getElSize(id) { //得到元素的size
                return new Promise((res, rej) => {
                    uni.createSelectorQuery().select("#" + id).fields({
                        size: true,
                        scrollOffset: true
                    }, (data) => {
                        res(data);
                    }).exec();
                })
            },
            async tapTab(e) { //点击tab-bar
                if (this.tabIndex === e.target.dataset.current) {
                    return false;
                } else {
                    let tabBar = await this.getElSize("tab-bar"),
                        tabBarScrollLeft = tabBar.scrollLeft; //点击的时候记录并设置scrollLeft
                    this.scrollLeft = tabBarScrollLeft;
                    this.isClickChange = true;
                    this.tabIndex = e.target.dataset.current
                }
            },
            randomfn() {
                let ary = [];
                for (let i = 0, length = this.tabBars.length; i < length; i++) {
                    let aryItem = {
                        loadingText: "加载更多...",
                        data: []
                    };
                    for (let j = 1; j <= 10; j++) {
                        aryItem.data.push(this['data' + Math.floor(Math.random() * 5)]);
                    }
                    ary.push(aryItem);
                }
                return ary;
            }
        }
    }
</script>

<style>
    page {
        display: flex;
    }

    .index {
        display: flex;
        flex: 1;
        flex-direction: column;
        overflow: hidden;
        height: 100%;
    }

    .list {
        width: 750upx;
        height: 100%;
    }

    .swiper-tab {
        width: 100%;
        white-space: nowrap;
        line-height: 100upx;
        height: 100upx;
        border-bottom: 1px solid #c8c7cc;
    }

    .swiper-tab-list {
        font-size: 30upx;
        width: 150upx;
        display: inline-block;
        text-align: center;
        color: #555;
    }

    .active {
        color: #007AFF;
    }

    .swiper-box {
        flex: 1;
        width: 100%;
        height: calc(100% - 100upx);
    }

    .loadmore {
        height: 70upx;
        width: 750upx;
        flex-direction: column;
        justify-content: center;
    }

    .loadmore-text {
        font-size: 30upx;
        text-align: center;
        color: #999999;
    }
</style>


Add a top margin to the hint button

Add a top margin to the hint button

<template v-if="itemId">

        <icon-button v-if="anyHints && availableHints > 0" @click="takeHint" class="hint-btn" :text="$tr('hint', {hintsLeft: availableHints})"/>
        <icon-button v-else-if="anyHints" class="hint-btn" disabled :text="$tr('noMoreHint')"/>

{{ $tr("hintLabel") }}

      <transition name="expand">

{{ message }}

      </transition>

      <icon-button v-if="scratchpad" id="scratchpad-show" :text="$tr('showScratch')"></icon-button>
      <icon-button v-else disabled id="scratchpad-not-available" :text="$tr('notAvailable')"></icon-button>

</template>


fix(tags): Simplify tag popup and remove transitions

re #37

<script>
import uuid from 'uuid'
import ElementNav from './elements/ElementNav.vue'
import EditElement from './elements/EditElement.vue'

function randomHexColor () {
  const low = 0xCC
  const range = 0xFF - low
  return '#' +
    Math.floor(Math.random() * range + low).toString(16) +
    Math.floor(Math.random() * range + low).toString(16) +
    Math.floor(Math.random() * range + low).toString(16)
}

export default {
  name: 'Game',
  components: {
    ElementNav,
    EditElement
  },
  props: {
    game: {
      type: Object,
      required: true
    },
    id: {
      type: String,
      required: false
    }
  },
  data () {
    return {
      nextTag: '',
      tagQuery: ''
    }
  },
  computed: {
    element () {
      const {game} = this
      return game.elements.find(e => e.id === this.id)
    },
    tags () {
      const {game, element} = this
      return element.tags.map(id => game.tags.find(t => t.id === id))
    },
    availableTags () {
      const {element, tagQuery} = this
      return this.game.tags
        .filter(t => element.tags.indexOf(t.id) === -1)
        .filter(t => t.title.toLowerCase().indexOf(tagQuery.toLowerCase()) > -1)
    }
  },
  methods: {
    createElement () {
      const {gameId, elements, nextElement} = this.game
      const id = `${nextElement}`
      const elem = {id, title: `Element ${id}`, markdown: ``, tags: []}
      elements.push(elem)
      this.game.nextElement++
      this.$router.push({name: 'game', params: {gameId, id}})
      this.$emit('change')
    },
    deleteElement ({id}) {
      const {gameId, elements, tags} = this.game
      this.elements = elements.filter(e => e.id === id)
      this.tags = tags.filter(t => elements.find(e => e.tags.find(id => id === t.id)))
      this.$router.push({ name: 'game', params: {gameId} })
      this.$emit('change')
    },
    setTagColor (id, color) {
      const tag = this.tags.find(t => t.id === id)
      tag.color = color
      this.$emit('change')
    },
    filterTags (query) {
      this.tagQuery = query
    },

    // current element actions

    setMarkdown (markdown) {
      this.element.markdown = markdown
      this.$emit('change')
    },

    createTag () {
      const {tags} = this.game
      const {element, nextTag} = this
      if (typeof nextTag === 'object') {
        element.tags.push(nextTag.id)
        this.$emit('change')
      } else if (tags.find(t => t.title.toLowerCase() === nextTag.toLowerCase())) {
        this.$message({message: 'Tag already exists.', type: 'warning'})
      } else {
        const id = uuid.v1()
        const color = randomHexColor()
        const title = nextTag
        tags.push({id, color, title})
        element.tags.push(id)
        this.$emit('change')
        this.$message({message: `"${title}" tag created.`, type: 'success'})
      }
      setImmediate(() => {
        this.nextTag = ''
        this.$refs.TagEntry.focus()
      })
    },

    removeTag (tagId) {
      const {element} = this
      const i = element.tags.findIndex(t => t === tagId)
      element.tags.splice(i, 1)

      const {elements, tags} = this.game
      const used = elements.find((e) => e.tags.find(t => t === tagId))
      if (!used) {
        const {title} = tags.find(t => t.id === tagId)
        this.game.tags = tags.filter(t => t.id !== tagId)
        this.$message({message: `"${title}" tag removed.`, type: 'warning'})
      }

      this.$emit('change')
    }
  }
}
</script>

<template>
  <el-container>

    <el-aside width="240px">
      <element-nav
        :gameId="game.gameId"
        :elements="game.elements"
        @create="createElement"
        @delete="deleteElement" />
    </el-aside>

    <el-main v-if="element">

      <el-form :inline="true" :model="element" size="mini">

        <el-form-item label="Title">
          <el-input
            v-model="element.title"
            placeholder="Element Title"
            @change="$emit('change')"/>
        </el-form-item>

        <el-form-item label="Tags">
          <el-select
            size="mini"
            filterable
            remote
            reserve-keyword
            allow-create
            default-first-option
            placeholder="New Tag..."
            loading-text="Loading"
            no-data-text="No data"
            no-match-text="No match"
            ref="TagEntry"
            v-model="nextTag"
            :loading="false"
            :remote-method="filterTags"
            @change="createTag">
            <el-option
              v-for="tag in availableTags"
              :key="tag.id"
              :label="tag.title"
              :value="tag">
            </el-option>
          </el-select>

        </el-form-item>

        <el-popover
          v-for="{id, title, color} in tags"
          :key="id"
          placement="top"
          trigger="click">

          <el-color-picker
            :value="color"
            @active-change="c => setTagColor(id, c)"
          />

          <el-tag
            disable-transitions
            slot="reference"
            type="info"
            size="small"
            closable
            :color="color"
            @close="removeTag(id)">
            {{title}}
          </el-tag>

        </el-popover>

        <el-tag v-if="element.tags.length === 0" size="small" type="info">Untagged</el-tag>
      </el-form>

      <edit-element :markdown="element.markdown" @edit="setMarkdown"/>
    </el-main>
  </el-container>
</template>

<style lang="scss" scoped>
  .el-container {
    .el-aside {
      overflow: inherit;
    }
    .el-main {
      padding: 4px;
      background-color: rgba(0, 0, 0, 0.1);
      display: flex;
      flex-flow: column;

      .el-tag {
        margin: 2px;
        cursor: pointer;
      }

      .edit-element {
        flex-grow: 1;
        margin-top: 4px;
      }
    }
  }
</style>

<template>
  <el-container>

    <el-aside width="240px">
      <element-nav
        :gameId="game.gameId"
        :elements="game.elements"
        @create="createElement"
        @delete="deleteElement" />
    </el-aside>

    <el-main v-if="element">

      <el-form :inline="true" :model="element" size="mini">

        <el-form-item label="Title">
          <el-input
            v-model="element.title"
            placeholder="Element Title"
            @change="$emit('change')"/>
        </el-form-item>

        <el-form-item label="Tags">
          <el-select
            size="mini"
            filterable
            remote
            reserve-keyword
            allow-create
            default-first-option
            placeholder="New Tag..."
            loading-text="Loading"
            no-data-text="No data"
            no-match-text="No match"
            ref="TagEntry"
            v-model="nextTag"
            :loading="false"
            :remote-method="filterTags"
            @change="createTag">
            <el-option
              v-for="tag in availableTags"
              :key="tag.id"
              :label="tag.title"
              :value="tag">
            </el-option>
          </el-select>

        </el-form-item>

        <el-popover
          v-for="{id, title, color} in tags"
          :key="id"
          placement="top"
          trigger="click">
          <el-color-picker
            style="float:right"
            :value="color"
            @active-change="c => setTagColor(id, c)"
          />

{{title}}

          {{id}}

          <el-tag
            slot="reference"
            type="info"
            size="small"
            closable
            :color="color"
            @close="removeTag(id)">
            {{title}}
          </el-tag>

        </el-popover>

        <el-tag v-if="element.tags.length === 0" size="small" type="info">Untagged</el-tag>

      </el-form>

      <edit-element :markdown="element.markdown" @edit="setMarkdown"/>
    </el-main>
  </el-container>
</template>


chore: Remove debug code

chore: Remove debug code

<template>

	<portal to="icon"><fa :icon="faBroadcastTower"/></portal>
	<portal to="title">{{ $t('announcements') }}</portal>

	<mk-pagination :pagination="pagination" #default="{items}" class="ruryvtyk" ref="list">

🆕 {{ announcement.title }}

				<mfm :text="announcement.text"/>

				<mk-button @click="read(items, announcement, i)" primary><fa :icon="faCheck"/> {{ $t('gotIt') }}</mk-button>

	</mk-pagination>

</template>


Actual cost of attendance

Actual cost of attendance

<template>

      Attendance for {{ scout.fullname }} (Troop {{ scout.troop }})

          Projected Cost of Attendance: {{ projectedCost | currency }}

Event Fee: {{ event.price | currency }}

Merit Badge Fees: {{ preferenceCosts | currency }}

Purchases: {{ purchaseCosts | currency }}

          Actual Cost of Attendance: {{ actualCost | currency }}

Event Fee: {{ event.price | currency }}

Merit Badge Fees: {{ assignmentCosts | currency }}

Purchases: {{ purchaseCosts | currency }}

        Merit Badge Preferences:

 0">
            {{ preference.details.rank }}: {{ preference.badge.name }}

              ({{ preference.price | currency }})

            This scout does not have any preferences listed for this event.

 0">
        Purchases:

            {{ purchase.item }}

              (Size: {{ purchase.details.size | capitalize }})

            {{ purchase.price | currency }} × {{ purchase.details.quantity }} =
            {{ purchase.price * purchase.details.quantity | currency }}

        Assignments:
        <div class="attendance-section" v-if="assignments.length < 1">

            {{ scout.fullname }} has not yet been assigned any merit badges for
            MBU {{ event.semester }} {{ event.year }}.

              Assign Merit Badges

              {{assignment.details.periods}} {{ assignment.badge.name }}

          <button class="button is-link"
                  @click="toggleState('assigning')">
            Edit Assignments
          </button>

    <assignment-create v-if="state === 'assigning'"
                       :scout="scout"
                       :event="event"
                       :preferences="preferences"
                       :registration="registration"
                       @done="toggleState('displaying')"></assignment-create>

</template>

<template>

      Attendance for {{ scout.fullname }} (Troop {{ scout.troop }})

          Projected Cost of Attendance: {{ projectedCost | currency }}

Event Fee: {{ event.price | currency }}

Merit Badge Fees: {{ preferenceCosts | currency }}

Purchases: {{ purchaseCosts | currency }}

        Merit Badge Preferences:

 0">
            {{ preference.details.rank }}: {{ preference.badge.name }}

              ({{ preference.price | currency }})

            This scout does not have any preferences listed for this event.

 0">
        Purchases:

            {{ purchase.item }}

              (Size: {{ purchase.details.size | capitalize }})

            {{ purchase.price | currency }} × {{ purchase.details.quantity }} =
            {{ purchase.price * purchase.details.quantity | currency }}

        Assignments:
        <div class="attendance-section" v-if="assignments.length < 1">

            {{ scout.fullname }} has not yet been assigned any merit badges for
            MBU {{ event.semester }} {{ event.year }}.

              Assign Merit Badges

    <assignment-create v-if="state === 'assigning'"
                       :scout="scout"
                       :event="event"
                       :preferences="preferences"
                       :registration="registration"
                       @done="toggleState('displaying')"></assignment-create>

</template>


Buffer/Network settings button icon + header spacing changes

<template>

        <template v-if="isChannel()">

{{buffer.name}}

                Join Channel

        </template>
        <template v-else-if="isServer()">

{{buffer.getNetwork().name}}

                Not connected.
                Connect

                Connecting...

        </template>
        <template v-else-if="isQuery()">

                Close

Private conversation with {{buffer.name}}

        </template>

            <tabbed-view>
                <tabbed-tab :header="'Settings'" :focus="true">
                    <channel-info v-bind:buffer="buffer"></channel-info>
                </tabbed-tab>
                <tabbed-tab :header="'Banned Users'">
                    <channel-banlist v-bind:buffer="buffer"></channel-banlist>
                </tabbed-tab>
                <tabbed-tab :header="'Notifications'">
                    <buffer-settings v-bind:buffer="buffer"></buffer-settings>
                </tabbed-tab>
            </tabbed-view>

</template>


fix redirect on click dropdown

fix redirect on click dropdown

<template>

      <slot></slot>

</template>

<template>

      <slot></slot>

</template>


fix(MdTable): sort on created (#1775)

fix(MdTable): sort on created (#1775)

fix #1774

<template>
  <md-tag-switcher :md-tag="contentTag" class="md-table">
    <slot name="md-table-toolbar" />

    <keep-alive>
      <md-table-alternate-header v-if="$scopedSlots['md-table-alternate-header'] && selectedCount">
        <slot name="md-table-alternate-header" :count="selectedCount" />
      </md-table-alternate-header>
    </keep-alive>

          <md-table-thead />

    <md-content class="md-table-content md-scrollbar" :class="contentClasses" :style="contentStyles" @scroll="setScroll">

        <md-table-thead :class="headerClasses" v-if="!mdFixedHeader && $scopedSlots['md-table-row']" />

          <slot />

          <md-table-row-ghost
            v-for="(item, index) in value"
            :key="getRowId(item, mdModelId)"
            :md-id="getRowId(item, mdModelId)"
            :md-index="index"
            :md-item="item">
            <slot name="md-table-row" :item="item" />
          </md-table-row-ghost>

              <slot name="md-table-empty-state" />

      <slot name="md-table-pagination" />
    </md-content>

    <slot v-if="!hasValue && $scopedSlots['md-table-row']" />
  </md-tag-switcher>
</template>


Setup doc block generation for plugin events

<template>
    <pre>
    /*
    {{ license }}
    */

    {{ pluginOutput }}
    </pre>
</template>

<script lang="ts">
import * as _ from 'lodash';
import { Component, Prop, Vue } from 'vue-property-decorator';
import IPlugin from '../lib/IPlugin';
import { CPPClass, CPPComment, CPPHelper, CPPFunction, CPPIfBlock, CPPVariable, CPPFormatter, CPPWritableObject, CPPVisibility, ILanguageWritable } from 'alyssa';
import { IPluginEvent } from '../lib/IPluginEvent';

@Component({
    name: 'plugin-generator'
})
export default class PluginGenerator extends Vue {
    private plugin: CPPClass;

    @Prop() pluginDefinition: IPlugin;

    get className() {
        if (this.pluginDefinition.name.trim().length === 0) {
            return 'SAMPLE_PLUGIN';
        }

        let literalNumbers = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];
        let pluginClassName = this.pluginDefinition.name.replace(/[^A-Za-z0-9_]/g, '');

        let tryParse = parseInt(pluginClassName.charAt(0));

        if (!isNaN(tryParse)) {
            pluginClassName = literalNumbers[tryParse] + pluginClassName.substring(1);
        }

        return pluginClassName;
    }

    get formatter() {
        let formatter = new CPPFormatter({
            indentWithSpaces: true,
            indentSpaceCount: 4
        });

        return this.pluginDefinition.formatter || formatter;
    }

    get license() {
        let currentYear = new Date().getFullYear().toString();

        if (this.pluginDefinition.license === null) {
            return `Copyright (C) ${currentYear} ${this.pluginDefinition.author}\nAll rights reserved.`;
        }

        return this.pluginDefinition.license.body
            .replace('{year}', currentYear)
            .replace('{author}', this.pluginDefinition.author)
            .replace('{name}', this.pluginDefinition.name);
    }

    get sortedEvents(): IPluginEvent[] {
        return _.sortBy(this.pluginDefinition.events, ['name']);
    }

    get pluginOutput() {
        this.plugin = new CPPClass(this.className);
        this.plugin.addExtends([CPPVisibility.Public, 'bz_Plugin']);

        this.buildNameFunction();
        this.buildInitFunction(this.sortedEvents);
        this.buildCleanupFunction();
        this.buildEventFunction(this.sortedEvents);

        return this.plugin.write(this.formatter, 0);
    }

    private buildNameFunction(): void {
        let fxn = new CPPFunction('const char*', 'Name');
        fxn.implementFunction([new CPPWritableObject(`return "${this.pluginDefinition.name || 'SAMPLE PLUGIN'}";`)]);

        fxn.setVirtual(true);
        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildInitFunction(events: IPluginEvent[]): void {
        let initBody: CPPWritableObject[] = [];

        // Register events
        events.forEach(function(event: IPluginEvent) {
            initBody.push(CPPHelper.createFunctionCall('Register', [event.name]));
        });

        // Register slash commands
        if (this.pluginDefinition.slashCommands.length > 0) {
            if (events.length > 0) {
                initBody.push(CPPHelper.createEmptyLine());
            }

            this.pluginDefinition.slashCommands.forEach(function(command: any) {
                initBody.push(CPPHelper.createFunctionCall('bz_registerCustomSlashCommand', [`"${command}"`, 'this']));
            });
        }

        // Build our Init() function
        let fxn = new CPPFunction('void', 'Init', [CPPVariable.createConstChar('config')]);
        fxn.implementFunction(initBody);
        fxn.setVirtual(true);
        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildCleanupFunction(): void {
        let fxn = new CPPFunction('void', 'Cleanup');
        fxn.implementFunction([new CPPWritableObject('Flush();')]);

        fxn.setVirtual(true);
        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildEventFunction(events: IPluginEvent[]): void {
        let fxn = new CPPFunction('void', 'Event', [new CPPVariable('bz_EventData*', 'eventData')]);
        fxn.setVirtual(true);
        fxn.implementFunction(this.buildEventLoop(events));

        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildEventLoop(events: IPluginEvent[], buildWithIfBlock: boolean = true): ILanguageWritable[] {
        if (buildWithIfBlock) {
            let block = new CPPIfBlock();

            events.forEach(function (event) {
                block.defineCondition(`eventData->eventType == ${event.dataType}`, PluginGenerator.buildEventBlock(event));
            });

            return [block];
        }

        return events && buildWithIfBlock ? [] : [];
    }

    private static buildEventBlock(event: IPluginEvent, showComments: boolean = true, buildDocBlock: boolean = true): ILanguageWritable[] {
        let body: ILanguageWritable[] = [];

        if (showComments) {
            body.push(new CPPComment([event.description], false));
        }

        body.push(new CPPVariable(event.dataType, '*data', `(${event.dataType}*)eventData`));

        if (buildDocBlock) {
            body.push(CPPHelper.createEmptyLine());

            let docBlock: string[] = [];

            docBlock.push('Data');
            docBlock.push('----');

            let dataTypeMaxLength = PluginGenerator.maxLengthArray(_.map(event.parameters, 'dataType'));
            let varNameMaxLength = PluginGenerator.maxLengthArray(_.map(event.parameters, 'name'));

            event.parameters.forEach(function (value) {
                let dataType = _.padEnd(`(${value.dataType})`, dataTypeMaxLength + 2);
                let varName = _.padEnd(value.name, varNameMaxLength);

                docBlock.push(`${dataType} ${varName} - ${value.description}`);
            });

            body.push(new CPPComment(docBlock, false));
        }

        return body;
    }

    private static maxLengthArray(elements: string[]): number {
        let max = elements.reduce(function (prev, curr) {
            return prev.length > curr.length ? prev : curr;
        });

        return max.length;
    }
}
</script>

<template>
    <pre>
    /*
    {{ license }}
    */

    {{ pluginOutput }}
    </pre>
</template>

<script lang="ts">
import * as _ from 'lodash';
import { Component, Prop, Vue } from 'vue-property-decorator';
import IPlugin from '../lib/IPlugin';
import { CPPClass, CPPHelper, CPPFunction, CPPVariable, CPPFormatter, CPPWritableObject, CPPVisibility } from 'alyssa';
import { IPluginEvent } from '../lib/IPluginEvent';

@Component({
    name: 'plugin-generator'
})
export default class PluginGenerator extends Vue {
    private plugin: CPPClass;

    @Prop() pluginDefinition: IPlugin;

    get className() {
        if (this.pluginDefinition.name.trim().length === 0) {
            return 'SAMPLE_PLUGIN';
        }

        let literalNumbers = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];
        let pluginClassName = this.pluginDefinition.name.replace(/[^A-Za-z0-9_]/g, '');

        let tryParse = parseInt(pluginClassName.charAt(0));

        if (!isNaN(tryParse)) {
            pluginClassName = literalNumbers[tryParse] + pluginClassName.substring(1);
        }

        return pluginClassName;
    }

    get formatter() {
        let formatter = new CPPFormatter({
            indentWithSpaces: true,
            indentSpaceCount: 4
        });

        return this.pluginDefinition.formatter || formatter;
    }

    get license() {
        let currentYear = new Date().getFullYear().toString();

        if (this.pluginDefinition.license === null) {
            return `Copyright (C) ${currentYear} ${this.pluginDefinition.author}\nAll rights reserved.`;
        }

        return this.pluginDefinition.license.body
            .replace('{year}', currentYear)
            .replace('{author}', this.pluginDefinition.author)
            .replace('{name}', this.pluginDefinition.name);
    }

    get sortedEvents(): IPluginEvent[] {
        return _.sortBy(this.pluginDefinition.events, ['name']);
    }

    get pluginOutput() {
        this.plugin = new CPPClass(this.className);
        this.plugin.addExtends([CPPVisibility.Public, 'bz_Plugin']);

        this.buildNameFunction();
        this.buildInitFunction(this.sortedEvents);
        this.buildCleanupFunction();
        this.buildEventFunction(this.sortedEvents);

        return this.plugin.write(this.formatter, 0);
    }

    private buildNameFunction(): void {
        let fxn = new CPPFunction('const char*', 'Name');
        fxn.implementFunction([new CPPWritableObject(`return "${this.pluginDefinition.name || 'SAMPLE PLUGIN'}";`)]);

        fxn.setVirtual(true);
        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildInitFunction(events: IPluginEvent[]): void {
        let initBody: CPPWritableObject[] = [];

        // Register events
        events.forEach(function(event: IPluginEvent) {
            initBody.push(CPPHelper.createFunctionCall('Register', [event.name]));
        });

        // Register slash commands
        if (this.pluginDefinition.slashCommands.length > 0) {
            if (events.length > 0) {
                initBody.push(CPPHelper.createEmptyLine());
            }

            this.pluginDefinition.slashCommands.forEach(function(command: any) {
                initBody.push(CPPHelper.createFunctionCall('bz_registerCustomSlashCommand', [`"${command}"`, 'this']));
            });
        }

        // Build our Init() function
        let fxn = new CPPFunction('void', 'Init', [CPPVariable.createConstChar('config')]);
        fxn.implementFunction(initBody);
        fxn.setVirtual(true);
        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildCleanupFunction(): void {
        let fxn = new CPPFunction('void', 'Cleanup');
        fxn.implementFunction([new CPPWritableObject('Flush();')]);

        fxn.setVirtual(true);
        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildEventFunction(events: IPluginEvent[]): void {
        let fxn = new CPPFunction('void', 'Event', [new CPPVariable('bz_EventData*', 'eventData')]);
        fxn.setVirtual(true);
        fxn.implementFunction(this.buildEventLoop(events));

        fxn.setParentClass(this.plugin, CPPVisibility.Public);
    }

    private buildEventLoop(events: IPluginEvent[], buildWithIfBlock: boolean = false): CPPWritableObject[] {
        return events && buildWithIfBlock ? [] : [];
    }
}
</script>


Fix new asset modal when asset list is empty

<template>

            {{ $t('assets.fields.episode') }}

          {{ $t('assets.fields.name') }}

            {{ $t('assets.fields.description') }}
            <button-simple
              class="is-small"
              icon="plus"
              :text="''"
              @click="onAddMetadataClicked"
              v-if="isCurrentUserAdmin && !isLoading"
            />

              {{ descriptor.name }}

              <chevron-down-icon
                @click="showMetadataHeaderMenu(descriptor.id, $event)"
                class="header-icon flexrow-item"
              />

              <router-link
                class="flexrow-item"
                style="margin-right: 0;"
                :to="taskTypePath(columnId)"
              >
                {{ !hiddenColumns[columnId] ? taskTypeMap[columnId].name : '' }}
              </router-link>
              <chevron-down-icon
                @click="showHeaderMenu(columnId, $event)"
                class="header-icon flexrow-item"
              />

            <button-link
              :class="{
                'is-small': true,
                highlighted: isEmptyTask
              }"
              icon="plus"
              :text="$t('tasks.create_tasks')"
              :path="createTasksPath"
              v-if="isCurrentUserManager && displayedAssets.length > 0 && !isLoading"
            />

{{ $t('assets.empty_list') }}

    <button-link
      class="level-item big-button"
      :text="$t('assets.new_assets')"
      :path="newAssetPath()"
    />

{{ $t('assets.empty_list_client') }}

            {{ group[0] ? group[0].asset_type_name : '' }}

            {{ episodeMap[asset.episode_id] ? episodeMap[asset.episode_id].name : $t('main.all') }}

            <entity-thumbnail :entity="asset" />

            <router-link
              class="asset-link"
              :to="assetPath(asset.id)"
            >
              {{ asset.name }}
            </router-link>

          <description-cell
            class="description"
            v-if="!isCurrentUserClient"
            :entry="asset"
          />

            {{ asset.data ? asset.data[descriptor.field_name] : '' }}

          <validation-cell
            :class="{
              'validation-cell': !hiddenColumns[columnId],
              'hidden-validation-cell': hiddenColumns[columnId]
            }"
            :key="columnId + '-' + asset.id"
            :ref="'validation-' + getIndex(i, k) + '-' + j"
            :column="taskTypeMap[columnId]"
            :entity="asset"
            :task-test="taskMap[asset.validations[columnId]]"
            :selected="assetSelectionGrid[getIndex(i, k)][j]"
            :rowX="getIndex(i, k)"
            :columnY="j"
            :minimized="hiddenColumns[columnId]"
            :is-static="true"
            @select="onTaskSelected"
            @unselect="onTaskUnselected"
            v-for="(columnId, j) in sortedValidationColumns"
          />
          <row-actions v-if="isCurrentUserManager"
            :entry="asset"
            :edit-route="editPath(asset.id)"
            :delete-route="deletePath(asset.id)"
            :restore-route="restorePath(asset.id)"
          />

    {{ displayedAssetsLength }} {{ $tc('assets.number', displayedAssetsLength) }}

</template>

<template>

            {{ $t('assets.fields.episode') }}

          {{ $t('assets.fields.name') }}

            {{ $t('assets.fields.description') }}
            <button-simple
              class="is-small"
              icon="plus"
              :text="''"
              @click="onAddMetadataClicked"
              v-if="isCurrentUserAdmin && !isLoading"
            />

              {{ descriptor.name }}

              <chevron-down-icon
                @click="showMetadataHeaderMenu(descriptor.id, $event)"
                class="header-icon flexrow-item"
              />

              <router-link
                class="flexrow-item"
                style="margin-right: 0;"
                :to="taskTypePath(columnId)"
              >
                {{ !hiddenColumns[columnId] ? taskTypeMap[columnId].name : '' }}
              </router-link>
              <chevron-down-icon
                @click="showHeaderMenu(columnId, $event)"
                class="header-icon flexrow-item"
              />

            <button-link
              :class="{
                'is-small': true,
                highlighted: isEmptyTask
              }"
              icon="plus"
              :text="$t('tasks.create_tasks')"
              :path="createTasksPath"
              v-if="isCurrentUserManager && displayedAssets.length > 0 && !isLoading"
            />

{{ $t('assets.empty_list') }}

    <button-link
      class="level-item big-button"
      :text="$t('assets.new_assets')"
      :path="{
        name: 'new-asset',
        params: {production_id: currentProduction.id}
      }"
    />

{{ $t('assets.empty_list_client') }}

            {{ group[0] ? group[0].asset_type_name : '' }}

            {{ episodeMap[asset.episode_id] ? episodeMap[asset.episode_id].name : $t('main.all') }}

            <entity-thumbnail :entity="asset" />

            <router-link
              class="asset-link"
              :to="assetPath(asset.id)"
            >
              {{ asset.name }}
            </router-link>

          <description-cell
            class="description"
            v-if="!isCurrentUserClient"
            :entry="asset"
          />

            {{ asset.data ? asset.data[descriptor.field_name] : '' }}

          <validation-cell
            :class="{
              'validation-cell': !hiddenColumns[columnId],
              'hidden-validation-cell': hiddenColumns[columnId]
            }"
            :key="columnId + '-' + asset.id"
            :ref="'validation-' + getIndex(i, k) + '-' + j"
            :column="taskTypeMap[columnId]"
            :entity="asset"
            :task-test="taskMap[asset.validations[columnId]]"
            :selected="assetSelectionGrid[getIndex(i, k)][j]"
            :rowX="getIndex(i, k)"
            :columnY="j"
            :minimized="hiddenColumns[columnId]"
            :is-static="true"
            @select="onTaskSelected"
            @unselect="onTaskUnselected"
            v-for="(columnId, j) in sortedValidationColumns"
          />
          <row-actions v-if="isCurrentUserManager"
            :entry="asset"
            :edit-route="editPath(asset.id)"
            :delete-route="deletePath(asset.id)"
            :restore-route="restorePath(asset.id)"
          />

    {{ displayedAssetsLength }} {{ $tc('assets.number', displayedAssetsLength) }}

</template>


handle focusIndex update properly

handle focusIndex update properly

<template>

    <tag-input v-model="keyword"
               v-if="!readonly"
               :index="-1"
               :focus-index="focusIndex"
               :selected="selected"
               :on-focus="onFocus"
               :on-blur="stopHandleInput">
    </tag-input>
    <template v-for="(tagItem, index) in tagsToShow">
      <tag :key="index"
           :tag="tagItem.tag"
           :index="index"
           :readonly="readonly || tagItem.readonly"
           @remove-tag="removeTag">
      </tag>
      <tag-input v-model="keyword"
                 v-if="!readonly"
                 :index="index"
                 :focus-index="focusIndex"
                 :selected="selected"
                 :on-focus="onFocus"
                 :on-blur="stopHandleInput"
                 @delete="removeTag">
      </tag-input>
    </template>
    <spinner class="spinner" v-if="requestCount && !simpleMode && spinner"></spinner>

        {{ item[labelKey] }}

没有结果，请更换关键词

</template>


fixed bug with compiling of Nofications.vue

<template>

    <transition-group name="fade-in-down">
      <div
        class="notification mt30 border-box cl-white"
        v-for="(notification, index) in notifications"
        :key="`${notification.type}_${index}`"
        :class="notification.type"
      >

          {{ notification.message }}

          <div
            class="py10 px20 pointer weight-400 action-border notification-action uppercase"
            :class="`border-${notification.type}`"
            id="notificationAction1"
            data-testid="notificationAction1"
            @click="execAction(notification.action1, index)"
          >
            {{ notification.action1.label }}

            {{ notification.action2.label }}

    </transition-group>

</template>

<template>

    <transition-group name="fade-in-down">

          {{ notification.message }}

          <div
            class="py10 px20 pointer weight-400 action-border notification-action uppercase"
            :class="`border-${notification.type}`"
            id="notificationAction1"
            data-testid="notificationAction1"
            @click="execAction(notification.action1, index)"
          >
            {{ notification.action1.label }}

            {{ notification.action2.label }}

    </transition-group>

</template>


Make percentage bigger

Make percentage bigger

<template>

{{ (progress * 100).toFixed() }}%

    <random-sentence></random-sentence>

</template>


link to blockscout rather than poaexplorer for POA contracts

<template>

      <media :query="{maxWidth: 500}">
        {{ address | truncate(20) }}
      </media>
      <media :query="{minWidth: 500}">
        {{ address }}
      </media>

    {{ copyText }}

</template>


modify: 更新 web-view 示例加载的页面

modify: 更新 web-view 示例加载的页面

<template>
    <view>
        <web-view src="https://uniapp.dcloud.io/static/web-view.html"></web-view>
    </view>
</template>

<script>
    export default {}
</script>

<style>

</style>

<template>
    <view>
        <web-view src="https://uniapp.dcloud.io"></web-view>
    </view>
</template>

<script>
    export default {}
</script>

<style>

</style>


don't try to be fancy with keyboard modality

<template>

  <div
    class="sort-widget"
    :class="{focused: hasFocus, 'not-focused': !hasFocus}"
  >
    <UiIconButton
      v-show="!isFirst"
      ref="upBtn"
      class="btn up"
      type="flat"
      :ariaLabel="moveUpText"
      :class="{visuallyhidden: !hasFocus}"
      @click="clickUp"
      @keyup.space="clickUp"
    >
      <mat-svg name="keyboard_arrow_up" category="hardware" />
    </UiIconButton>
    <!--
      Currently missing from material icon repo.
      See https://github.com/google/material-design-icons/issues/786
     -->
    <file-svg src="./drag_indicator.svg" class="grip" />
    <UiIconButton
      v-show="!isLast"
      ref="dnBtn"
      class="btn dn"
      type="flat"
      :ariaLabel="moveDownText"
      :class="{visuallyhidden: !hasFocus}"
      @click="clickDown"
      @keyup.space="clickDown"
    >
      <mat-svg name="keyboard_arrow_down" category="hardware" />
    </UiIconButton>
  </div>

</template>

<script>

  import UiIconButton from 'kolibri.coreVue.components.UiIconButton';

  export default {
    name: 'KDragSortWidget',
    components: {
      UiIconButton,
    },
    props: {
      moveUpText: {
        type: String,
        required: true,
      },
      moveDownText: {
        type: String,
        required: true,
      },
      isFirst: {
        type: Boolean,
        required: true,
      },
      isLast: {
        type: Boolean,
        required: true,
      },
    },
    data() {
      return {
        hasFocus: false,
      };
    },
    mounted() {
      window.addEventListener('focus', this.updateFocus, true);
    },
    destroyed() {
      window.removeEventListener('focus', this.updateFocus, true);
    },
    methods: {
      updateFocus() {
        this.hasFocus = [this.$refs.dnBtn.$el, this.$refs.upBtn.$el].includes(
          document.activeElement
        );
      },
      clickDown() {
        this.$emit('moveDown');
        this.$nextTick(() => {
          if (this.isLast) {
            this.$refs.upBtn.$el.focus();
          } else {
            this.$refs.dnBtn.$el.focus();
          }
        });
      },
      clickUp() {
        this.$emit('moveUp');
        this.$nextTick(() => {
          if (this.isFirst) {
            this.$refs.dnBtn.$el.focus();
          } else {
            this.$refs.upBtn.$el.focus();
          }
        });
      },
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .sort-widget {
    position: relative;
  }

  .grip {
    transition: opacity $core-time ease;
    transform: rotate(90deg);
  }

  .btn {
    position: absolute;
    left: -6px;
    z-index: 2;
    transition: opacity $core-time ease;
  }

  // only dim the grip when the keyboard is being used
  .focused .grip {
    opacity: 0.15;
  }

  // also hide the buttons when the keyboard is not being used
  .not-focused .btn {
    opacity: 0;
  }

  .up {
    top: -16px;
  }

  .dn {
    top: 4px;
  }

</style>

<template>

  <div
    class="sort-widget"
    :class="{focused: hasFocus, 'not-focused': !hasFocus}"
  >
    <UiIconButton
      v-show="!isFirst"
      ref="upBtn"
      class="btn up"
      type="flat"
      :ariaLabel="moveUpText"
      :class="{visuallyhidden: !hasFocus}"
      @click="clickUp"
      @keyup.space="clickUp"
    >
      <mat-svg name="keyboard_arrow_up" category="hardware" />
    </UiIconButton>
    <!--
      Currently missing from material icon repo.
      See https://github.com/google/material-design-icons/issues/786
     -->
    <file-svg src="./drag_indicator.svg" class="grip" />
    <UiIconButton
      v-show="!isLast"
      ref="dnBtn"
      class="btn dn"
      type="flat"
      :ariaLabel="moveDownText"
      :class="{visuallyhidden: !hasFocus}"
      @click="clickDown"
      @keyup.space="clickDown"
    >
      <mat-svg name="keyboard_arrow_down" category="hardware" />
    </UiIconButton>
  </div>

</template>

<script>

  import UiIconButton from 'kolibri.coreVue.components.UiIconButton';

  export default {
    name: 'KDragSortWidget',
    components: {
      UiIconButton,
    },
    props: {
      moveUpText: {
        type: String,
        required: true,
      },
      moveDownText: {
        type: String,
        required: true,
      },
      isFirst: {
        type: Boolean,
        required: true,
      },
      isLast: {
        type: Boolean,
        required: true,
      },
    },
    data() {
      return {
        hasFocus: false,
      };
    },
    mounted() {
      window.addEventListener('focus', this.updateFocus, true);
    },
    destroyed() {
      window.removeEventListener('focus', this.updateFocus, true);
    },
    methods: {
      updateFocus() {
        this.hasFocus = [this.$refs.dnBtn.$el, this.$refs.upBtn.$el].includes(
          document.activeElement
        );
      },
      clickDown() {
        this.$emit('moveDown');
        this.$nextTick(() => {
          if (this.isLast) {
            this.$refs.upBtn.$el.focus();
          } else {
            this.$refs.dnBtn.$el.focus();
          }
        });
      },
      clickUp() {
        this.$emit('moveUp');
        this.$nextTick(() => {
          if (this.isFirst) {
            this.$refs.dnBtn.$el.focus();
          } else {
            this.$refs.upBtn.$el.focus();
          }
        });
      },
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .sort-widget {
    position: relative;
  }

  .grip {
    transition: opacity $core-time ease;
    transform: rotate(90deg);
  }

  .btn {
    position: absolute;
    left: -6px;
    z-index: 2;
    transition: opacity $core-time ease;
  }

  // only dim the grip when the keyboard is being used
  body[modality='keyboard'] .focused .grip {
    opacity: 0.15;
  }

  // also hide the buttons when the keyboard is not being used
  .not-focused .btn,
  body:not([modality='keyboard']) .btn {
    opacity: 0;
  }

  .up {
    top: -16px;
  }

  .dn {
    top: 4px;
  }

</style>


Fix quotes

Fix quotes

<template>

{{ $tr('classExams', {className}) }}

    <k-select
      :label="$tr('exams')"
      :options="statusOptions"
      :inline="true"
      v-model="statusSelected"
    />
    <k-button
      :primary="true"
      appearance="raised-button"
      class="create-button"
      @click="openCreateExamModal"
      :text="$tr('newExam')"
    />

          {{ $tr('title') }}
          {{ $tr('visibleTo') }}

        <exam-row
          v-for="exam in filteredExams"
          :key="exam.id"
          :examId="exam.id"
          :examTitle="exam.title"
          :examActive="exam.active"
          :examVisibility="exam.visibility"
          @changeExamVisibility="openChangeExamVisibilityModal"
          @activateExam="openActivateExamModal"
          @deactivateExam="openDeactivateExamModal"
          @previewExam="openPreviewExamModal"
          @viewReport="routeToExamReport(exam)"
          @renameExam="openRenameExamModal"
          @deleteExam="openDeleteExamModal"
        />

{{ $tr('noExams') }}

    <create-exam-modal
      v-if="showCreateExamModal"
      :classId="classId"
      :channels="sortedChannels"
    />
    <activate-exam-modal
      v-if="showActivateExamModal"
      :examId="selectedExam.id"
      :examTitle="selectedExam.title"
      :examVisibility="selectedExam.visibility"
      :classId="classId"
    />
    <deactivate-exam-modal
      v-if="showDeactivateExamModal"
      :examId="selectedExam.id"
      :examTitle="selectedExam.title"
      :examVisibility="selectedExam.visibility"
      :classId="classId"
    />
    <change-exam-visibility-modal
      v-if="showChangeExamVisibilityModal"
      :examId="selectedExam.id"
      :examTitle="selectedExam.title"
      :examVisibility="selectedExam.visibility"
      :classId="classId"
      :className="className"
      :classGroups="currentClassGroups"
    />
    <preview-exam-modal
      v-if="showPreviewExamModal"
      :examChannelId="selectedExam.channelId"
      :examQuestionSources="selectedExam.questionSources"
      :examSeed="selectedExam.seed"
      :examNumQuestions="selectedExam.questionCount"
    />
    <rename-exam-modal
      v-if="showRenameExamModal"
      :examId="selectedExam.id"
      :examTitle="selectedExam.title"
      :classId="classId"
      :exams="sortedExams"
    />
    <delete-exam-modal
      v-if="showDeleteExamModal"
      :examId="selectedExam.id"
      :examTitle="selectedExam.title"
      :classId="classId"
    />

</template>


Fix search box on IE

Fix search box on IE

<template>

  <form
    class="search-box"
    @submit.prevent="search"
    @keydown.esc.prevent="handleEscKey"
  >

      {{ $tr('searchBoxLabel') }}
      <input
        v-model="searchQuery"
        id="searchfield"
        type="search"
        class="search-input"
        ref="searchInput"
        :placeholder="$tr('searchBoxLabel')"
      >

        <ui-icon-button
          icon="clear"
          color="black"
          size="small"
          class="search-clear-button"
          :class="searchQuery === '' ? '' : 'search-clear-button-visble'"
          :ariaLabel="$tr('clearButtonLabel')"
          @click="searchQuery = ''"
        />

          <ui-icon-button
            type="secondary"
            color="white"
            class="search-submit-button"
            :class="{ 'rtl-icon': icon === 'arrow_forward' && isRtl }"
            :icon="icon"
            :ariaLabel="$tr('startSearchButtonLabel')"
            @click="search"
          />

  </form>

</template>


updates to content page - vary rendering based on page mode

<template>

    <breadcrumbs
      v-if="pageMode === $options.PageModes.EXPLORE"
      :rootid='rootTopicId'
      :crumbs='breadcrumbs'
      :current='title'>
    </breadcrumbs>

      Home

{{ title }}

        {{ description }}

      <content-render
        :id="id"
        :kind="kind"
        :files="files"
        :content-id="contentId"
        :available="available"
        :extra-fields="extraFields">
      </content-render>

    <card-grid header='Recommended' v-if="pageMode === $options.PageModes.LEARN">
      <content-card
        v-for="content in recommended"
        :id="content.id"
        :title="content.title"
        :thumbnail="content.thumbnail"
        :kind="content.kind"
        :progress="content.progress">
      </content-card>
    </card-grid>

</template>

<template>

    <breadcrumbs
      v-if="pageMode === $options.PageModes.EXPLORE"
      :rootid='rootTopicId'
      :crumbs='breadcrumbs'
      :current='title'>
    </breadcrumbs>

      Home

        {{ description }}

      <content-render
        :id="id"
        :kind="kind"
        :files="files"
        :content-id="contentId"
        :available="available"
        :extra-fields="extraFields">
      </content-render>

    <card-grid>
      <content-card
        v-for="content in recommended"
        :id="content.id"
        :title="content.title"
        :thumbnail="content.thumbnail"
        :kind="content.kind"
        :progress="content.progress">
      </content-card>
    </card-grid>

</template>


updated mgmt top-nav :focus styling

updated mgmt top-nav :focus styling

<template>

      Users

      Data

      Content

</template>