Fix repo overview License Coverage (And some spaces?)

Look at line 116 for change

Signed-off-by: Matt Snell <0571cfbefc19de26e906feb2eaf27aeab74db0a0@unomaha.edu>

<template>
  <d-container fluid class="main-content-container px-4">
    <d-breadcrumb class="repo_Overview">
      <d-breadcrumb-item :active="false" :text="base.rg_name" href="#" @click="onRepoGroup({rg_name: base.rg_name, repo_group_id: base.repo_group_id})"/>
      <d-breadcrumb-item :active="true" :text="base.repo_name" href="#" />

    </d-breadcrumb>

    <compare-control></compare-control>

    <d-row>

      <d-col cols="12" md="6" lg="6" sm="12">
        <d-card>

          <d-container>
            <d-row>
              <d-col style="">
                <d-card-body title="Lines of code added by the top 10 authors as Percentages - By Time Period" class="text-center">

                  <spinner v-if="!loadedBars"></spinner>

                  <normalized-stacked-bar-chart
                  v-if="loadedBars"
                  source="changesByAuthor1"
                  :data="values['changesByAuthor']"
                  ></normalized-stacked-bar-chart>

                </d-card-body>
              </d-col>
            </d-row>

            <d-row>
              <d-col style="">
                <d-card-body title="Average Lines of Code Per Commit" class="text-center">

                  <spinner v-if="!loadedBars"></spinner>

                  <horizontal-bar-chart
                  v-if="loadedBars"
                  measure="lines"
                  title="Average Lines of Code Per Commit"
                  source="changesByAuthor2"
                  :data="values['changesByAuthor']"
                  ></horizontal-bar-chart>

                </d-card-body>
              </d-col>
            </d-row>

          </d-container>

        </d-card>
      </d-col>
      <d-col cols="12" md="6" lg="6" sm="12">

        <coverage-card title="License Coverage" source="sbom"
        sourcetwo="licenseDeclared"></coverage-card>

        <d-card>
          <d-card-body title="Lines of code added by the top 10 authors" class="text-center">

            <spinner v-if="!loadedBars"></spinner>

            <lines-of-code-chart
              v-if="loadedBars"
              :data="values['changesByAuthor']"
              class="repo_Overview_Chart"
            ></lines-of-code-chart>

          </d-card-body>
        </d-card>
      </d-col>

    </d-row>

    <d-row>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="pull_requests"
                      source="reviews"
                      title="Reviews (Pull Requests) / Week"
                      cite-url=""
                      cite-text="Reviews"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="pull_requests"
                      source="reviewsAccepted"
                      title="Reviews (Pull Requests) Accepted / Week"
                      cite-url=""
                      cite-text="Reviews Accepted"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="pull_requests"
                      source="reviewsDeclined"
                      title="Reviews (Pull Requests) Declined / Week"
                      cite-url=""
                      cite-text="Reviews Declined"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="open_count"
                      source="openIssuesCount"
                      title="Open Issues / Week"
                      cite-url=""
                      cite-text="Open Issues"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="closed_count"
                      source="closedIssuesCount"
                      title="Closed Issues / Week"
                      cite-url=""
                      cite-text="Closed Issues"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="issues"
                      source="issuesNew"
                      title="New Issues / Week"
                      cite-url=""
                      cite-text="New Issues"
          ></dynamic-line-chart>
        </d-card>

        <d-card>

          <dynamic-line-chart
                      source="codeChanges"
                      title="Code Changes (Commits) / Week"
                      cite-url=""
                      cite-text="Code Changes"
                      filedTime="date"
                      fieldCount="commit_count"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      source="codeChangesLines"
                      title="Lines of Code Added / Week"
                      cite-url=""
                      cite-text="Code Changes Lines"
                      filedTime="date"
                      fieldCount="added"
          ></dynamic-line-chart>
        </d-card>

    </d-row>

  </d-container>
</template>

<template>
  <d-container fluid class="main-content-container px-4">
    <d-breadcrumb class="repo_Overview">
      <d-breadcrumb-item :active="false" :text="base.rg_name" href="#" @click="onRepoGroup({rg_name: base.rg_name, repo_group_id: base.repo_group_id})"/>
      <d-breadcrumb-item :active="true" :text="base.repo_name" href="#" />

    </d-breadcrumb>

    <compare-control></compare-control>

    <d-row>

      <d-col cols="12" md="6" lg="6" sm="12">
        <d-card>

          <d-container>
            <d-row>
              <d-col style="">
                <d-card-body title="Lines of code added by the top 10 authors as Percentages - By Time Period" class="text-center">

                  <spinner v-if="!loadedBars"></spinner>

                  <normalized-stacked-bar-chart
                  v-if="loadedBars"
                  source="changesByAuthor1"
                  :data="values['changesByAuthor']"
                  ></normalized-stacked-bar-chart>

                </d-card-body>
              </d-col>
            </d-row>

            <d-row>
              <d-col style="">
                <d-card-body title="Average Lines of Code Per Commit" class="text-center">

                  <spinner v-if="!loadedBars"></spinner>

                  <horizontal-bar-chart
                  v-if="loadedBars"
                  measure="lines"
                  title="Average Lines of Code Per Commit"
                  source="changesByAuthor2"
                  :data="values['changesByAuthor']"
                  ></horizontal-bar-chart>

                </d-card-body>
              </d-col>
            </d-row>

          </d-container>

        </d-card>
      </d-col>
      <d-col cols="12" md="6" lg="6" sm="12">

        <coverage-card title="License Coverage" source="sbom"></coverage-card>

        <d-card>
          <d-card-body title="Lines of code added by the top 10 authors" class="text-center">

            <spinner v-if="!loadedBars"></spinner>

            <lines-of-code-chart
              v-if="loadedBars"
              :data="values['changesByAuthor']"
              class="repo_Overview_Chart"
            ></lines-of-code-chart>

          </d-card-body>
        </d-card>
      </d-col>

    </d-row>

    <d-row>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="pull_requests"
                      source="reviews"
                      title="Reviews (Pull Requests) / Week"
                      cite-url=""
                      cite-text="Reviews"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="pull_requests"
                      source="reviewsAccepted"
                      title="Reviews (Pull Requests) Accepted / Week"
                      cite-url=""
                      cite-text="Reviews Accepted"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="pull_requests"
                      source="reviewsDeclined"
                      title="Reviews (Pull Requests) Declined / Week"
                      cite-url=""
                      cite-text="Reviews Declined"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="open_count"
                      source="openIssuesCount"
                      title="Open Issues / Week"
                      cite-url=""
                      cite-text="Open Issues"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="closed_count"
                      source="closedIssuesCount"
                      title="Closed Issues / Week"
                      cite-url=""
                      cite-text="Closed Issues"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      filedTime="date"
                      fieldCount="issues"
                      source="issuesNew"
                      title="New Issues / Week"
                      cite-url=""
                      cite-text="New Issues"
          ></dynamic-line-chart>
        </d-card>

        <d-card>

          <dynamic-line-chart
                      source="codeChanges"
                      title="Code Changes (Commits) / Week"
                      cite-url=""
                      cite-text="Code Changes"
                      filedTime="date"
                      fieldCount="commit_count"
          ></dynamic-line-chart>
        </d-card>

        <d-card>
          <dynamic-line-chart
                      source="codeChangesLines"
                      title="Lines of Code Added / Week"
                      cite-url=""
                      cite-text="Code Changes Lines"
                      filedTime="date"
                      fieldCount="added"
          ></dynamic-line-chart>
        </d-card>

    </d-row>

  </d-container>
</template>


some issua at v-tree

some issua at v-tree

<template>
    <v-dialog
      v-model="open"
      class="mpu"
      width="200"
      persistent
    >

      <v-card>
        <v-card-title
          class="headline grey lighten-2">
          Move To
           <v-card-actions>
                <v-btn icon light flat small color="red" @click.native="open = false">
                    <v-icon>close</v-icon>
                </v-btn>
            </v-card-actions>

        </v-card-title>

        <v-card-text class="recursive-folder">
           <v-treeview
            v-model="moveid"
            :items="getNestedFolders"
            activatable
            active-class="grey lighten-4 indigo--text"
            selected-color="indigo"
            open-on-click
            selectable
            expand-icon="mdi-chevron-down"
            on-icon="mdi-bookmark"
            off-icon="mdi-bookmark-outline"
            indeterminate-icon="mdi-bookmark-minus"
          >
          </v-treeview>

        </v-card-text>

        <v-card-actions>
          <v-btn
            color="error"
            flat
            @click="open = false"
          >
            Cancel
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
</template>

<script>
import Mixin from './mixin';
import { mapState, mapGetters } from 'vuex';
import RecursiveFolder from './RecursiveFolder.vue';

export default {
    $_veeValidate: {
      validator: 'new'
    },
    components: {
        RecursiveFolder
    },
    data () {
        return {
            active: [],
            name: '',
            open: false,
            moveid: 0,
        }
    },
    mixins: [Mixin],
    mounted () {
        Bus.$on('moveTo', ()=> {
            this.open = !this.open;
        });

        // console.log(this.getNestedFolders);
    },
    computed: {
        ...mapState('Media', ['folders']),
        ...mapGetters( 'Media', ['getNestedFolders'] )

    },
    methods: {
        onSubmit () {
            this.$validator.validateAll();
            const item = {
                name: this.name,
                parent_id: this.currentFolderId
            }

            this.$store.dispatch('Media/addFolder', item)
                .then(() => {
                    this.open = false
                })

        },
        nestedView () {
            return
        }
    }
}
</script>

<style>
.mpu .v-card__actions {
    border-top: 1px solid #ddd;
}

</style>

<template>
    <v-dialog
      v-model="open"
      class="mpu"
      width="200"
      persistent
    >

      <v-card>
        <v-card-title
          class="headline grey lighten-2">
          Move To
           <v-card-actions>
                <v-btn icon light flat small color="red" @click.native="open = false">
                    <v-icon>close</v-icon>
                </v-btn>
            </v-card-actions>

        </v-card-title>

        <v-card-text class="recursive-folder">

            <ul class="recursive-folder-ul ">
                <recursive-folder v-for="folder in getNestedFolders" :key="folder.id"  :folder="folder"></recursive-folder>
            </ul>

        </v-card-text>

        <v-card-actions>
          <v-btn
            color="error"
            flat
            @click="open = false"
          >
            Cancel
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
</template>

<script>
import Mixin from './mixin';
import { mapState, mapGetters } from 'vuex';
import RecursiveFolder from './RecursiveFolder.vue';

export default {
    $_veeValidate: {
      validator: 'new'
    },
    components: {
        RecursiveFolder
    },
    data () {
        return {
            name: '',
            open: false,
        }
    },
    mixins: [Mixin],
    mounted () {
        Bus.$on('moveTo', ()=> {
            this.open = !this.open;
        });

        // console.log(this.getNestedFolders);
    },
    computed: {
        ...mapState('Media', ['folders']),
        ...mapGetters( 'Media', ['getNestedFolders'] )

    },
    methods: {
        onSubmit () {
            this.$validator.validateAll();
            const item = {
                name: this.name,
                parent_id: this.currentFolderId
            }

            this.$store.dispatch('Media/addFolder', item)
                .then(() => {
                    this.open = false
                })

        },
        nestedView () {
            return
        }
    }
}
</script>

<style>
.mpu .v-card__actions {
    border-top: 1px solid #ddd;
}

.recursive-folder ul {
    list-style: none;
    display: block;
    padding: 0;
    margin: 0;
}
.recursive-folder ul li {
    list-style: none;
    padding: 2px 0px;
    margin: 0px;
}
</style>


Update theme.vue

Update theme.vue

<template>

<fa :icon="faPalette"/> {{ $t('theme') }}

				<input type="checkbox" class="dn" id="dn" v-model="darkMode" :disabled="syncDeviceDarkMode"/>

					{{ $t('light') }}
					{{ $t('dark') }}

		<mk-select v-model="lightTheme">
			<template #label>{{ $t('themeForLightMode') }}</template>
			<optgroup :label="$t('lightThemes')">
				<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
			</optgroup>
			<optgroup :label="$t('darkThemes')">
				<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
			</optgroup>
		</mk-select>
		<mk-select v-model="darkTheme">
			<template #label>{{ $t('themeForDarkMode') }}</template>
			<optgroup :label="$t('darkThemes')">
				<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
			</optgroup>
			<optgroup :label="$t('lightThemes')">
				<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
			</optgroup>
		</mk-select>

		<mk-switch v-model="syncDeviceDarkMode">{{ $t('syncDeviceDarkMode') }}</mk-switch>

		<mk-button primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</mk-button>
		<mk-button primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</mk-button>

</template>


Adding adition

Adding adition

<template>

Props

              type

                  <select id="type" class="form-control" v-model="type">
                    <option>tree</option>
                    <option>cluster</option>
                  </select>

              layoutType

                  <select id="layout-type" class="form-control" v-model="layoutType">
                    <option>horizontal</option>
                    <option>vertical</option>
                    <option>circular</option>
                  </select>

              nodeTextDisplay

                  <select id="layout-type" class="form-control" v-model="nodeTextDisplay">
                    <option>all</option>
                    <option>leaves</option>
                    <option>extremities</option>
                  </select>

              linkLayout

                  <select id="layout-type" class="form-control" v-model="linkLayout">
                    <option>bezier</option>
                    <option>orthogonal</option>
                  </select>

              marginx

                <input id="margin-x" class="form-control" type="range" min="0" max="200" v-model.number="Marginx">

{{Marginx}}px

              marginy

                <input id="margin-y" class="form-control" type="range" min="0" max="200" v-model.number="Marginy">

{{Marginy}}px

              radius

                <input id="radius" class="form-control" type="range" min="1" max="10" v-model.number="radius">

{{radius}}px

              Duration

                <input id="velocity" class="form-control" type="range" min="0" max="3000" v-model.number="duration">

{{duration}}ms

              leafTextMargin

                <input id="leaf-text-margin" class="form-control" type="range" min="0" max="100" v-model.number="leafTextMargin">

{{leafTextMargin}}px

              nodeTextMargin

                <input id="node-text-margin" class="form-control" type="range" min="0" max="100" v-model.number="nodeTextMargin">

{{nodeTextMargin}}px

              minZoom

                <input id="minZoom" class="form-control" type="range" min="0.01" max="1" step="0.05" v-model.number="minZoom">

{{minZoom}}

              maxZoom

                <input id="maxZoom" class="form-control" type="range" min="1" max="100" v-model.number="maxZoom">

{{maxZoom}}

              Zoomable
              <input id="zoomable" class="form-check-input" type="checkbox" v-model="zoomable">

              Current Node: {{currentData.data.text}}
              No Node selected.

            <button type="button" :disabled="!currentData" class="btn btn-primary" @click="expandAll" data-toggle="tooltip" data-placement="top" title="Expand All from current">

            </button>

            <button type="button" :disabled="!currentData" class="btn btn-secondary" @click="collapseAll" data-toggle="tooltip" data-placement="top" title="Collapse All from current">

            </button>

            <button type="button" :disabled="!currentData" class="btn btn-success" @click="showOnly" data-toggle="tooltip" data-placement="top" title="Show Only from current">

            </button>

            <button type="button" :disabled="!currentData" class="btn btn-warning" @click="show" data-toggle="tooltip" data-placement="top" title="Show current">

            </button>

            <button v-if="zoomable" type="button" class="btn btn-warning" @click="resetZoom" data-toggle="tooltip" data-placement="top" title="Reset Zoom">

            </button>

            <button type="button" class="btn btn-danger" @click="gremlins" data-toggle="tooltip" data-placement="top" :title="isUnderGremlinsAttack ? 'stop attack' :'unleash gremlins'">

            </button>

Events

Name: {{event.eventName}} Data:{{event.data.text}}

    <tree ref="tree"
      v-model="currentData"
      :nodeTextDisplay="nodeTextDisplay"
      :identifier="getId"
      :nodeTextMargin="nodeTextMargin"
      :zoomable="zoomable"
      :data="Graph.tree"
      :leafTextMargin="leafTextMargin"
      :node-text="nodeText"
      :margin-x="Marginx"
      :margin-y="Marginy"
      :radius="radius"
      :type="type"
      :layout-type="layoutType"
      :linkLayout="linkLayout"
      :duration="duration"
      :minZoom="minZoom"
      :maxZoom="maxZoom"
      contextMenuPlacement="bottom-start"
      class="tree"
      @clickedText="onClick"
      @expand="onExpand"
      @retract="onRetract"
      @clickedNode="onClickNode">

      <template #contextMenu="{data}">

           <button type="button" class="btn btn-primary" @click="addFor(data)" data-toggle="tooltip" title="Add children">

            </button>

      </template>
    </tree>

</template>

<template>

Props

              type

                  <select id="type" class="form-control" v-model="type">
                    <option>tree</option>
                    <option>cluster</option>
                  </select>

              layoutType

                  <select id="layout-type" class="form-control" v-model="layoutType">
                    <option>horizontal</option>
                    <option>vertical</option>
                    <option>circular</option>
                  </select>

              nodeTextDisplay

                  <select id="layout-type" class="form-control" v-model="nodeTextDisplay">
                    <option>all</option>
                    <option>leaves</option>
                    <option>extremities</option>
                  </select>

              linkLayout

                  <select id="layout-type" class="form-control" v-model="linkLayout">
                    <option>bezier</option>
                    <option>orthogonal</option>
                  </select>

              marginx

                <input id="margin-x" class="form-control" type="range" min="0" max="200" v-model.number="Marginx">

{{Marginx}}px

              marginy

                <input id="margin-y" class="form-control" type="range" min="0" max="200" v-model.number="Marginy">

{{Marginy}}px

              radius

                <input id="radius" class="form-control" type="range" min="1" max="10" v-model.number="radius">

{{radius}}px

              Duration

                <input id="velocity" class="form-control" type="range" min="0" max="3000" v-model.number="duration">

{{duration}}ms

              leafTextMargin

                <input id="leaf-text-margin" class="form-control" type="range" min="0" max="100" v-model.number="leafTextMargin">

{{leafTextMargin}}px

              nodeTextMargin

                <input id="node-text-margin" class="form-control" type="range" min="0" max="100" v-model.number="nodeTextMargin">

{{nodeTextMargin}}px

              minZoom

                <input id="minZoom" class="form-control" type="range" min="0.01" max="1" step="0.05" v-model.number="minZoom">

{{minZoom}}

              maxZoom

                <input id="maxZoom" class="form-control" type="range" min="1" max="100" v-model.number="maxZoom">

{{maxZoom}}

              Zoomable
              <input id="zoomable" class="form-check-input" type="checkbox" v-model="zoomable">

              Current Node: {{currentData.data.text}}
              No Node selected.

            <button type="button" :disabled="!currentData" class="btn btn-primary" @click="expandAll" data-toggle="tooltip" data-placement="top" title="Expand All from current">

            </button>

            <button type="button" :disabled="!currentData" class="btn btn-secondary" @click="collapseAll" data-toggle="tooltip" data-placement="top" title="Collapse All from current">

            </button>

            <button type="button" :disabled="!currentData" class="btn btn-success" @click="showOnly" data-toggle="tooltip" data-placement="top" title="Show Only from current">

            </button>

            <button type="button" :disabled="!currentData" class="btn btn-warning" @click="show" data-toggle="tooltip" data-placement="top" title="Show current">

            </button>

            <button v-if="zoomable" type="button" class="btn btn-warning" @click="resetZoom" data-toggle="tooltip" data-placement="top" title="Reset Zoom">

            </button>

            <button type="button" class="btn btn-danger" @click="gremlins" data-toggle="tooltip" data-placement="top" :title="isUnderGremlinsAttack ? 'stop attack' :'unleash gremlins'">

            </button>

Events

Name: {{event.eventName}} Data:{{event.data.text}}

    <tree ref="tree"
      v-model="currentData"
      :nodeTextDisplay="nodeTextDisplay"
      :identifier="getId"
      :nodeTextMargin="nodeTextMargin"
      :zoomable="zoomable"
      :data="Graph.tree"
      :leafTextMargin="leafTextMargin"
      :node-text="nodeText"
      :margin-x="Marginx"
      :margin-y="Marginy"
      :radius="radius"
      :type="type"
      :layout-type="layoutType"
      :linkLayout="linkLayout"
      :duration="duration"
      :minZoom="minZoom"
      :maxZoom="maxZoom"
      class="tree"
      @clickedText="onClick"
      @expand="onExpand"
      @retract="onRetract"
      @clickedNode="onClickNode">

      <template #contextMenu="{data}">

{{data.text}}

      </template>
    </tree>

</template>


[FIX] use string for list key

[FIX] use string for list key

<template>
  <ovtemplate>

       正在读
      <icon class="i-ov-icon" name="book" scale="2"></icon>

      <book-list v-for="book in booklist" :key="book.title" :book="book">
      </book-list>
      我的豆瓣

  </ovtemplate>
</template>

<template>
  <ovtemplate>

       正在读
      <icon class="i-ov-icon" name="book" scale="2"></icon>

      <book-list v-for="book in booklist" :key="book" :book="book">
      </book-list>
      我的豆瓣

  </ovtemplate>
</template>


Adding conditional superuser statement to EditUserModal.

<template>

  <KModal
    :title="$tr('editUser')"
    :submitText="$tr('save')"
    :cancelText="$tr('cancel')"
    :submitDisabled="isBusy"
    @submit="submitForm"
    @cancel="displayModal(false)"
  >
    <KTextbox
      ref="name"
      type="text"
      :label="$tr('fullName')"
      :autofocus="true"
      :maxlength="120"
      :invalid="nameIsInvalid"
      :invalidText="nameIsInvalidText"
      @blur="nameBlurred = true"
      v-model="newName"
    />

    <KTextbox
      ref="username"
      type="text"
      :label="$tr('username')"
      :maxlength="30"
      :invalid="usernameIsInvalid"
      :invalidText="usernameIsInvalidText"
      @blur="usernameBlurred = true"
      @input="setError(null)"
      v-model="newUsername"
    />

    <template v-if="kind === UserKinds.SUPERUSER">

        {{ $tr('userType') }}

      <UserTypeDisplay
        :userType="kind"
        class="user-type"
      />

        Go to Device permissions to change this

    </template>
    <template v-else>
      <KSelect
        :label="$tr('userType')"
        :options="userKindOptions"
        v-model="newKind"
      />

      <fieldset class="coach-selector" v-if="coachIsSelected">
        <KRadioButton
          :label="$tr('classCoachLabel')"
          :description="$tr('classCoachDescription')"
          :value="true"
          v-model="classCoachIsSelected"
        />
        <KRadioButton
          :label="$tr('facilityCoachLabel')"
          :description="$tr('facilityCoachDescription')"
          :value="false"
          v-model="classCoachIsSelected"
        />
      </fieldset>
    </template>

  </KModal>

</template>

<template>

  <KModal
    :title="$tr('editUser')"
    :submitText="$tr('save')"
    :cancelText="$tr('cancel')"
    :submitDisabled="isBusy"
    @submit="submitForm"
    @cancel="displayModal(false)"
  >
    <KTextbox
      ref="name"
      type="text"
      :label="$tr('fullName')"
      :autofocus="true"
      :maxlength="120"
      :invalid="nameIsInvalid"
      :invalidText="nameIsInvalidText"
      @blur="nameBlurred = true"
      v-model="newName"
    />

    <KTextbox
      ref="username"
      type="text"
      :label="$tr('username')"
      :maxlength="30"
      :invalid="usernameIsInvalid"
      :invalidText="usernameIsInvalidText"
      @blur="usernameBlurred = true"
      @input="setError(null)"
      v-model="newUsername"
    />

    <KSelect
      :label="$tr('userType')"
      :options="userKinds"
      v-model="newKind"
    />

    <fieldset class="coach-selector" v-if="coachIsSelected">
      <KRadioButton
        :label="$tr('classCoachLabel')"
        :description="$tr('classCoachDescription')"
        :value="true"
        v-model="classCoachIsSelected"
      />
      <KRadioButton
        :label="$tr('facilityCoachLabel')"
        :description="$tr('facilityCoachDescription')"
        :value="false"
        v-model="classCoachIsSelected"
      />
    </fieldset>
  </KModal>

</template>

<script>

  import { mapActions, mapState, mapGetters } from 'vuex';
  import { UserKinds, ERROR_CONSTANTS } from 'kolibri.coreVue.vuex.constants';
  import { validateUsername } from 'kolibri.utils.validators';
  import KModal from 'kolibri.coreVue.components.KModal';
  import KTextbox from 'kolibri.coreVue.components.KTextbox';
  import KSelect from 'kolibri.coreVue.components.KSelect';
  import KRadioButton from 'kolibri.coreVue.components.KRadioButton';

  export default {
    name: 'EditUserModal',
    $trs: {
      editUser: 'Edit user',
      fullName: 'Full name',
      username: 'Username',
      userType: 'User type',
      admin: 'Admin',
      coach: 'Coach',
      learner: 'Learner',
      save: 'Save',
      cancel: 'Cancel',
      required: 'This field is required',
      usernameAlreadyExists: 'Username already exists',
      usernameNotAlphaNumUnderscore: 'Username can only contain letters, numbers, and underscores',
      classCoachLabel: 'Class coach',
      classCoachDescription: "Can only instruct classes that they're assigned to",
      facilityCoachLabel: 'Facility coach',
      facilityCoachDescription: 'Can instruct all classes in your facility',
    },
    components: {
      KModal,
      KTextbox,
      KSelect,
      KRadioButton,
    },
    props: {
      id: {
        type: String,
        required: true,
      },
      name: {
        type: String,
        required: true,
      },
      username: {
        type: String,
        required: true,
      },
      kind: {
        type: String,
        required: true,
      },
    },
    data() {
      return {
        classCoachIsSelected: true,
        newName: this.name,
        newUsername: this.username,
        newKind: null,
        nameBlurred: false,
        usernameBlurred: false,
        formSubmitted: false,
      };
    },
    computed: {
      ...mapGetters(['currentFacilityId']),
      ...mapState({
        currentUserId: state => state.core.session.user_id,
        currentUserKind: state => state.core.session.kind[0],
      }),
      ...mapState('userManagement', ['facilityUsers', 'error', 'isBusy']),
      coachIsSelected() {
        return this.newKind.value === UserKinds.COACH;
      },
      userKinds() {
        return [
          {
            label: this.$tr('learner'),
            value: UserKinds.LEARNER,
          },
          {
            label: this.$tr('coach'),
            value: UserKinds.COACH,
          },
          {
            label: this.$tr('admin'),
            value: UserKinds.ADMIN,
          },
        ];
      },
      nameIsInvalidText() {
        if (this.nameBlurred || this.formSubmitted) {
          if (this.newName === '') {
            return this.$tr('required');
          }
        }
        return '';
      },
      nameIsInvalid() {
        return Boolean(this.nameIsInvalidText);
      },
      usernameAlreadyExists() {
        // Just return if it's the same username with a different case
        if (this.username.toLowerCase() === this.newUsername.toLowerCase()) {
          return false;
        }

        if (this.error) {
          if (this.error.includes(ERROR_CONSTANTS.USERNAME_ALREADY_EXISTS)) {
            return true;
          }
        }

        return this.facilityUsers.find(
          ({ username }) => username.toLowerCase() === this.newUsername.toLowerCase()
        );
      },
      usernameIsInvalidText() {
        if (this.usernameBlurred || this.formSubmitted) {
          if (this.newUsername === '') {
            return this.$tr('required');
          }
          if (this.usernameAlreadyExists) {
            return this.$tr('usernameAlreadyExists');
          }
          if (!validateUsername(this.newUsername)) {
            return this.$tr('usernameNotAlphaNumUnderscore');
          }
        }
        return '';
      },
      usernameIsInvalid() {
        return Boolean(this.usernameIsInvalidText);
      },
      formIsValid() {
        return !this.nameIsInvalid && !this.usernameIsInvalid;
      },
    },
    beforeMount() {
      const coachOption = this.userKinds[1];
      if (this.kind === UserKinds.ASSIGNABLE_COACH) {
        this.newKind = coachOption;
        this.classCoachIsSelected = true;
      } else if (this.kind === UserKinds.COACH) {
        this.newKind = coachOption;
        this.classCoachIsSelected = false;
      } else {
        this.newKind = this.userKinds.find(kind => kind.value === this.kind);
      }
    },
    methods: {
      ...mapActions('userManagement', ['updateUser', 'displayModal', 'setError']),
      submitForm() {
        const roleUpdate = {
          collection: this.currentFacilityId,
        };
        this.formSubmitted = true;
        if (this.formIsValid) {
          if (this.newKind.value === UserKinds.COACH) {
            if (this.classCoachIsSelected) {
              roleUpdate.kind = UserKinds.ASSIGNABLE_COACH;
            } else {
              roleUpdate.kind = UserKinds.COACH;
            }
          } else {
            roleUpdate.kind = this.newKind.value;
          }
          this.updateUser({
            userId: this.id,
            updates: {
              username: this.newUsername,
              full_name: this.newName,
              role: roleUpdate,
            },
          });
          if (
            this.currentUserId === this.id &&
            this.currentUserKind !== UserKinds.SUPERUSER &&
            this.newKind.value === UserKinds.LEARNER
          ) {
            window.location.href = window.location.origin;
          }
        } else {
          if (this.nameIsInvalid) {
            this.$refs.name.focus();
          } else if (this.usernameIsInvalid) {
            this.$refs.username.focus();
          }
        }
      },
    },
  };

</script>

<style lang="scss" scoped>

  .coach-selector {
    padding: 0;
    margin: 0;
    margin-bottom: 3em;
    border: 0;
  }

  .edit-user-form {
    min-height: 350px;
  }

</style>


new solution for vec3picker

new solution for vec3picker

<script>
import noop from '../../utils/noop';
import clamp from '../../utils/clamp';
import { multiplyMatrixByVector, getInverseMatrix, getViewMatrix } from '../../utils/matrix';
import { vectorLength, normalizeVector } from '../../utils/vector';
import getElementOffsets from '../../utils/getElementOffsets';

export default {
  name: 'Vec3Picker',
  props: {
    name: String,
    vector: [0, 0, 0],
    dimension: {
      type: Number,
      default: 400
    },
    onChange: {
      type: Function,
      default: noop
    }
  },
  data() {
    return {
      canvas: null,
      canvasOffsets: null,
      ctx: null,

      rotationSpeed: 0.008,

      isMouseDown: false,

      viewMatrix: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
      center: [0, 0, 0],

      width: this.dimension,
      height: this.dimension,

      edgeLength: 0.5 * this.dimension - 40,
      edgeIndent: 0.5 * this.dimension - 30,

      pointVector: [0, 0, 0],

      pointX: 20,
      pointY: -25,

      thetaX: 0.512,
      thetaY: -0.464,

      startX: 0,
      startY: 0,

      dx: 0.512,
      dy: -0.464
    };
  },
  methods: {
    drawAxis(adgesVector, adgesTextVector, axisName) {
      const adgeTextVector = multiplyMatrixByVector(this.viewMatrix, adgesTextVector);
      const egdeVector = multiplyMatrixByVector(this.viewMatrix, adgesVector);

      this.ctx.fillText(axisName, adgeTextVector[0], -1 * adgeTextVector[1]);
      this.ctx.save();
      this.ctx.strokeStyle = '#000';
      this.ctx.beginPath();
      this.ctx.moveTo(this.center[0], -1 * this.center[1]);
      this.ctx.lineTo(egdeVector[0], -1 * egdeVector[1]);
      this.ctx.stroke();
      this.ctx.restore();
    },

    drawAxes() {
      this.ctx.textBaseline = 'middle';
      this.ctx.textAlign = 'center';
      this.ctx.fillStyle = '#fff';

      this.drawAxis([this.edgeLength, 0, 0], [this.edgeIndent, 0, 0], 'X');
      this.drawAxis([-this.edgeLength, 0, 0], [-this.edgeIndent, 0, 0], '-X');
      this.drawAxis([0, this.edgeLength, 0], [0, this.edgeIndent, 0], 'Y');
      this.drawAxis([0, -this.edgeLength, 0], [0, -this.edgeIndent, 0], '-Y');
      this.drawAxis([0, 0, this.edgeLength], [0, 0, this.edgeIndent], 'Z');
      this.drawAxis([0, 0, -this.edgeLength], [0, 0, -this.edgeIndent], '-Z');
    },

    onMouseDown(event) {
      this.canvasOffsets = getElementOffsets(this.canvas);
      this.isMouseDown = true;

      this.startX = event.pageX - this.canvasOffsets.left;
      this.startY = event.pageY - this.canvasOffsets.top;
    },

    onMouseMove(event) {
      if (this.isMouseDown) {
        this.ctx.clearRect(-this.width / 2, -this.height / 2, this.width, this.height);

        const x = clamp(event.pageX - this.canvasOffsets.left, 0, this.width);
        const y = clamp(event.pageY - this.canvasOffsets.top, 0, this.height);

        this.thetaX = this.rotationSpeed * (y - this.startY) + this.dx;
        this.thetaY = this.rotationSpeed * (x - this.startX) + this.dy;

        this.viewMatrix = getViewMatrix(this.thetaX, this.thetaY);
        this.drawAxes();
      }
    },

    onMouseUp() {
      this.isMouseDown = false;
      this.dx = this.thetaX;
      this.dy = this.thetaY;
    }
  },

  mounted() {
    this.canvas = this.$refs.vec3Picker;
    this.canvasOffsets = getElementOffsets(this.canvas);
    this.ctx = this.canvas.getContext('2d');

    this.ctx.translate(this.width / 2, this.height / 2);

    this.viewMatrix = getViewMatrix(this.thetaX, this.thetaY);
    this.drawAxes();

    this.canvas.addEventListener('mousedown', event => this.onMouseDown(event));
    document.addEventListener('mousemove', event => this.onMouseMove(event));
    document.addEventListener('mouseup', event => this.onMouseUp(event));
  },

  beforeDestroy() {
    this.canvas.removeEventListener('mousedown', event => this.onMouseDown(event));
    document.removeEventListener('mousemove', event => this.onMouseMove(event));
    document.removeEventListener('mouseup', event => this.onMouseUp(event));
  }
};
</script>

<template>
  <canvas ref="vec3Picker" class="vec3-picker" :width="width" :height="height"></canvas>
</template>

<script>
import noop from '../../utils/noop';
import clamp from '../../utils/clamp';
import { getRotationXMatrix, getRotationYMatrix, multiplyMatrices, multiplyMatrixByVector, getInverseMatrix } from '../../utils/matrix';
import { vectorLength, normalizeVector } from '../../utils/vector';
import getElementOffsets from '../../utils/getElementOffsets';

export default {
  name: 'Vec3Picker',
  props: {
    name: String,
    vector: [0, 0, 0],
    onChange: {
      type: Function,
      default: noop
    }
  },
  data() {
    return {
      canvas: null,
      canvasOffsets: null,
      ctx: null,

      isShiftPress: false,
      isMouseDown: false,
      isMouseDownOnPoint: false,

      viewMatrix: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
      center: [0, 0, 0],

      width: 400,
      height: 400,

      pointVector: [0, 0, 0],

      pointX: 20,
      pointY: -25,

      currentX: 0,
      currentY: 0,

      previousX: 0,
      previousY: 0,

      thetaX: 0.35,
      thetaY: -0.35,

      dx: 0,
      dy: 0,

      x: 0,
      y: 0
    };
  },
  methods: {
    drawAxis(adgesVector, adgesTextVector, axisName) {
      const adgeTextVector = multiplyMatrixByVector(this.viewMatrix, adgesTextVector);
      const egdeVector = multiplyMatrixByVector(this.viewMatrix, adgesVector);

      this.ctx.fillText(axisName, adgeTextVector[0], -1 * adgeTextVector[1]);
      this.ctx.save();
      this.ctx.strokeStyle = '#000';
      this.ctx.beginPath();
      this.ctx.moveTo(this.center[0], -1 * this.center[1]);
      this.ctx.lineTo(egdeVector[0], -1 * egdeVector[1]);
      this.ctx.stroke();
      this.ctx.restore();
    },

    drawPoint() {
      this.ctx.save();
      this.ctx.strokeStyle = '#08c';
      this.ctx.beginPath();
      this.ctx.setLineDash([5, 2.5]);
      this.ctx.moveTo(this.center[0], -1 * this.center[1]);
      this.ctx.lineTo(this.pointVector[0], -1 * this.pointVector[1]);
      this.ctx.stroke();
      this.ctx.restore();

      this.ctx.beginPath();
      this.ctx.arc(this.pointVector[0], -1 * this.pointVector[1], 2, 0, 2 * Math.PI, false);
      this.ctx.fill();
    },

    drawAxes() {
      this.ctx.textBaseline = 'middle';
      this.ctx.textAlign = 'center';
      this.ctx.fillStyle = '#fff';

      const edgeLength = (this.width / 2) - 40;
      const edgeLengthWithIndent = edgeLength + 10;

      this.drawAxis([edgeLength, 0, 0], [edgeLengthWithIndent, 0, 0], 'X');
      this.drawAxis([-edgeLength, 0, 0], [-edgeLengthWithIndent, 0, 0], '-X');
      this.drawAxis([0, edgeLength, 0], [0, edgeLengthWithIndent, 0], 'Y');
      this.drawAxis([0, -edgeLength, 0], [0, -edgeLengthWithIndent, 0], '-Y');
      this.drawAxis([0, 0, edgeLength], [0, 0, edgeLengthWithIndent], 'Z');
      this.drawAxis([0, 0, -edgeLength], [0, 0, -edgeLengthWithIndent], '-Z');
    },

    onMouseDown(event) {
      this.canvasOffsets = getElementOffsets(this.canvas);

      if (this.isShiftPress) {
        this.isMouseDownOnPoint = true;
      } else {
        this.isMouseDown = true;
      }
    },

    onMouseMove(event) {
      if (this.isMouseDownOnPoint || this.isMouseDown) {
        this.currentX = clamp(event.pageX - this.canvasOffsets.left, 0, this.width);
        this.currentY = clamp(event.pageY - this.canvasOffsets.top, 0, this.height);

        if (this.isMouseDownOnPoint) {
          this.ctx.clearRect(-this.width / 2, -this.height / 2, this.width, this.height);

          this.drawAxes();

          this.pointX = this.currentX - (this.width / 2);
          this.pointY = this.currentY - (this.height / 2);

          const inversMatrix = getInverseMatrix(this.viewMatrix);
          this.pointVector = multiplyMatrixByVector(inversMatrix, [this.pointX, -this.pointY, 0]);

          this.drawPoint();
        }

        if (this.isMouseDown) {
          this.ctx.clearRect(-this.width / 2, -this.height / 2, this.width, this.height);

          const normalizedX = this.currentX / this.width;
          const normalizedY = this.currentY / this.height;

          if (this.previousX && this.previousY) {
            this.dx = normalizedX - this.previousX;
            this.dy = normalizedY - this.previousY;
          }

          this.previousX = normalizedX;
          this.previousY = normalizedY;

          this.x += this.dx;
          this.y += this.dy;

          this.thetaX = this.y * Math.PI;
          this.thetaY = this.x * Math.PI;

          this.viewMatrix = multiplyMatrices(getRotationXMatrix(this.thetaX), getRotationYMatrix(this.thetaY));
          this.drawAxes();

          this.pointVector = multiplyMatrixByVector(this.viewMatrix, [this.pointX, this.pointY, 0]);
          this.drawPoint();
        }
      }
    },

    onMouseUp() {
      this.isMouseDown = false;
      this.isMouseDownOnPoint = false;
      this.previousX = 0;
      this.previousY = 0;
    },

    onKeyDown(e) {
      if (e.keyCode === 16) {
        this.isShiftPress = true;
      }
    },

    onKeyUp(e) {
      if (e.keyCode === 16) {
        this.isShiftPress = false;
      }
    }
  },
  mounted() {
    this.canvas = this.$refs.vec3Picker;
    this.canvasOffsets = getElementOffsets(this.canvas);
    this.ctx = this.canvas.getContext('2d');

    this.ctx.translate(this.width / 2, this.height / 2);

    this.viewMatrix = multiplyMatrices(getRotationXMatrix(this.thetaX), getRotationYMatrix(this.thetaY));
    this.drawAxes();

    this.pointVector = multiplyMatrixByVector(this.viewMatrix, [this.pointX, this.pointY, 0]);
    this.drawPoint();

    this.canvas.addEventListener('mousedown', event => this.onMouseDown(event));
    document.addEventListener('mousemove', event => this.onMouseMove(event));
    document.addEventListener('mouseup', event => this.onMouseUp(event));

    document.addEventListener('keydown', event => this.onKeyDown(event));
    document.addEventListener('keyup', event => this.onKeyUp(event));
  },
  beforeDestroy() {
    this.canvas.removeEventListener('mousedown', event => this.onMouseDown(event));
    document.removeEventListener('mousemove', event => this.onMouseMove(event));
    document.removeEventListener('mouseup', event => this.onMouseUp(event));

    document.removeEventListener('keydown', event => this.onKeyDown(event));
    document.removeEventListener('keyup', event => this.onKeyUp(event));
  }
};
</script>

<template>
  <canvas ref="vec3Picker" class="vec3-picker" :width="width" :height="height"></canvas>
</template>


Sorting restored

Sorting restored


Fixes error on adding a model to the list

<template>

Loading...

        <vehicle-models-form :edit="modelToUpdate !== null" :model="modelToUpdate" :brands="brands"
                             @model-created="addModel" @model-updated="updateModel">
        </vehicle-models-form>

</template>


Add imperfect sound test to admin menu

Add imperfect sound test to admin menu

<template>

            <template v-for="team in teams">

                        {{ team.name }}

            </template>

</template>


fix sorting

fix sorting

<script lang="coffee">
import Records            from '@/shared/services/records'
import AbilityService     from '@/shared/services/ability_service'
import EventBus           from '@/shared/services/event_bus'
import RecordLoader       from '@/shared/services/record_loader'
import ThreadFilter       from '@/shared/services/thread_filter'
import DiscussionModalMixin     from '@/mixins/discussion_modal'
import { applyLoadingFunction } from '@/shared/helpers/apply'
import { map, debounce, orderBy } from 'lodash'
import Session from '@/shared/services/session'
import WatchRecords from '@/mixins/watch_records'
import UrlFor       from '@/mixins/url_for'

export default
  mixins: [DiscussionModalMixin, WatchRecords, UrlFor]

  created: -> @init()

  data: ->
    group: Records.groups.fuzzyFind(@$route.params.key)
    discussions: []
    searchResults: []
    loader: null
    showClosed: false
    showUnread: false
    fragment: ''
    includeSubgroups: false

  methods:
    init: ->
      @loader = new RecordLoader
        collection: 'discussions'
        params:
          group_id: @group.id

      @searchLoader = new RecordLoader
        collection: 'searchResults'
        params:
          group_id: @group.id

      @fetch()

      @watchRecords
        key: @group.id
        collections: ['discussions', 'groups']
        query: (store) => @query(store)

      # @maxDiscussions = if AbilityService.canViewPrivateContent(@group)
      #   @group.discussionsCount
      # else
      #   @group.publicDiscussionsCount

    query: (store) ->
      if @fragment
        results = Records.searchResults.collection.chain().
                    find(query: @fragment).
                    find(resultGroupName: {$in: @groupNames}).data()

        @searchResults = orderBy(results, 'rank', 'desc')
      else
        chain = Records.discussions.collection.chain()

        if @includeSubgroups
          chain = chain.find(groupId: {$in: @group.organisationIds()})
        else
          chain = chain.find(groupId: @group.id)

        if @showClosed
          chain = chain.find(closedAt: {$ne: null})
        else
          chain = chain.find(closedAt: null)

        if @showUnread
          chain = chain.where (discussion) -> discussion.isUnread()

        chain = chain.compoundsort([['pinned', true], ['lastActivityAt', true]])

        @discussions = chain.data()

    fetch: debounce ->
      if @fragment
        params = {q: @fragment}
        params.include_subgroups = @includeSubgroups
        @searchLoader.fetchRecords params
      else
        params = {from: @from}
        params.filter = 'show_closed' if @showClosed
        params.include_subgroups = @includeSubgroups
        @loader.fetchRecords(params)
    ,
      300

  watch:
    fragment: ->
      @from = 0
      @loader.numRequested = 0
      @searchLoader.numRequested = 0
      @fetch()
      @query()

    showUnread: ->
      @from = 0
      @loader.numRequested = 0
      @fetch()
      @query()

    showClosed: ->
      @from = 0
      @loader.numRequested = 0
      @fetch()
      @query()

    includeSubgroups: ->
      @from = 0
      @loader.numRequested = 0
      @fetch()
      @query()

    group: (a, b) ->
      @init() if a.id != b.id

    isLoggedIn: -> @fetch()

  computed:
    groupNames: ->
      if @fragment
        if @includeSubgroups
          @group.subgroups().map (g) -> g.name
        else
          [@group.name]

    loading: ->
      @loader.loading || @searchLoader.loading

    noThreads: ->
      !@loading && @discussions.length == 0

    canViewPrivateContent: ->
      AbilityService.canViewPrivateContent(@group)

    canStartThread: ->
      AbilityService.canStartThread(@group)

    isLoggedIn: ->
      Session.isSignedIn()

</script>

<template lang="pug">
.discussions-panel
  v-toolbar(flat)
    //- v-toolbar-items
    v-text-field(solo flat append-icon="mdi-magnify" v-model="fragment" :label="$t('common.action.search')")
    v-switch(v-model="showClosed" :label="$t('discussions_panel.closed')")
    v-switch(v-if="group.hasSubgroups()" v-model="includeSubgroups" :label="$t('discussions_panel.include_subgroups')")
    v-switch(v-model="showUnread" :label="$t('discussions_panel.unread')")
    v-spacer
    v-btn.discussions-panel__new-thread-button(@click= 'openStartDiscussionModal(group)' outline color='primary' v-if='canStartThread' v-t="'navbar.start_thread'")

  v-progress-linear(indeterminate :active="loading")

  .discussions-panel__content(v-if="!fragment")
    v-alert.discussions-panel__list--empty(v-if='noThreads' :value="true" color="info" icon="info")
      p(v-t="'group_page.no_threads_here'")
      p(v-if='!canViewPrivateContent', v-t="'group_page.private_threads'")
    .discussions-panel__list(v-if="discussions.length")
      section.thread-preview-collection__container
        thread-preview-collection(:threads='discussions', :limit='loader.numRequested')
      v-btn.discussions-panel__show-more(v-if="!loader.exhausted" :disabled="loader.loading" @click='loader.loadMore()', v-t="{ path: 'common.action.show_more' }")
      .lmo-hint-text.discussions-panel__no-more-threads(v-t="{ path: 'group_page.no_more_threads' }", v-if='loader.numLoaded > 0 && loader.exhausted')

  .discussions-panel__content(v-if="fragment")
    v-alert.discussions-panel__list--empty(v-if='!searchResults.length' :value="true" color="info" icon="info")
      p(v-t="'group_page.no_threads_here'")
    v-list(two-line v-for="result in searchResults" :key="result.id")
      v-list-tile.thread-preview.thread-preview__link(:to="urlFor(result)")
        v-list-tile-content
          v-list-tile-title {{result.title}} {{result.rank}}
          v-list-tile-sub-title
            span(v-html="result.resultGroupName")
            |
            | ·
            |
            time-ago(:date='result.lastActivityAt')
            |
            | ·
            |
            span(v-html="result.blurb")
</template>

<script lang="coffee">
import Records            from '@/shared/services/records'
import AbilityService     from '@/shared/services/ability_service'
import EventBus           from '@/shared/services/event_bus'
import RecordLoader       from '@/shared/services/record_loader'
import ThreadFilter       from '@/shared/services/thread_filter'
import DiscussionModalMixin     from '@/mixins/discussion_modal'
import { applyLoadingFunction } from '@/shared/helpers/apply'
import { map, debounce, sortBy } from 'lodash'
import Session from '@/shared/services/session'
import WatchRecords from '@/mixins/watch_records'
import UrlFor       from '@/mixins/url_for'

export default
  mixins: [DiscussionModalMixin, WatchRecords, UrlFor]

  created: -> @init()

  data: ->
    group: Records.groups.fuzzyFind(@$route.params.key)
    discussions: []
    searchResults: []
    loader: null
    showClosed: false
    showUnread: false
    fragment: ''
    includeSubgroups: false

  methods:
    init: ->
      @loader = new RecordLoader
        collection: 'discussions'
        params:
          group_id: @group.id

      @searchLoader = new RecordLoader
        collection: 'searchResults'
        params:
          group_id: @group.id

      @fetch()

      @watchRecords
        key: @group.id
        collections: ['discussions', 'groups']
        query: (store) => @query(store)

      # @maxDiscussions = if AbilityService.canViewPrivateContent(@group)
      #   @group.discussionsCount
      # else
      #   @group.publicDiscussionsCount

    query: (store) ->
      if @fragment
        results = Records.searchResults.collection.chain().
                    find(query: @fragment).
                    find(resultGroupName: {$in: @groupNames}).data()

        @searchResults = sortBy(results, ['-rank', '-lastActivityAt'])
      else
        chain = Records.discussions.collection.chain()

        if @includeSubgroups
          chain = chain.find(groupId: {$in: @group.organisationIds()})
        else
          chain = chain.find(groupId: @group.id)

        if @showClosed
          chain = chain.find(closedAt: {$ne: null})
        else
          chain = chain.find(closedAt: null)

        if @showUnread
          chain = chain.where (discussion) -> discussion.isUnread()

        chain = chain.compoundsort([['pinned', true], ['lastActivityAt', true]])

        @discussions = chain.data()

    fetch: debounce ->
      if @fragment
        params = {q: @fragment}
        params.include_subgroups = @includeSubgroups
        @searchLoader.fetchRecords params
      else
        params = {from: @from}
        params.filter = 'show_closed' if @showClosed
        params.include_subgroups = @includeSubgroups
        @loader.fetchRecords(params)
    ,
      300

  watch:
    fragment: ->
      @from = 0
      @loader.numRequested = 0
      @searchLoader.numRequested = 0
      @fetch()
      @query()

    showUnread: ->
      @from = 0
      @loader.numRequested = 0
      @fetch()
      @query()

    showClosed: ->
      @from = 0
      @loader.numRequested = 0
      @fetch()
      @query()

    includeSubgroups: ->
      @from = 0
      @loader.numRequested = 0
      @fetch()
      @query()

    group: (a, b) ->
      @init() if a.id != b.id

    isLoggedIn: -> @fetch()

  computed:
    groupNames: ->
      if @fragment
        if @includeSubgroups
          @group.subgroups().map (g) -> g.name
        else
          [@group.name]

    loading: ->
      @loader.loading || @searchLoader.loading

    noThreads: ->
      !@loading && @discussions.length == 0

    canViewPrivateContent: ->
      AbilityService.canViewPrivateContent(@group)

    canStartThread: ->
      AbilityService.canStartThread(@group)

    isLoggedIn: ->
      Session.isSignedIn()

</script>

<template lang="pug">
.discussions-panel
  v-toolbar(flat)
    //- v-toolbar-items
    v-text-field(solo flat append-icon="mdi-magnify" v-model="fragment" :label="$t('common.action.search')")
    v-switch(v-model="showClosed" :label="$t('discussions_panel.closed')")
    v-switch(v-if="group.hasSubgroups()" v-model="includeSubgroups" :label="$t('discussions_panel.include_subgroups')")
    v-switch(v-model="showUnread" :label="$t('discussions_panel.unread')")
    v-spacer
    v-btn.discussions-panel__new-thread-button(@click= 'openStartDiscussionModal(group)' outline color='primary' v-if='canStartThread' v-t="'navbar.start_thread'")

  v-progress-linear(indeterminate :active="loading")

  .discussions-panel__content(v-if="!fragment")
    v-alert.discussions-panel__list--empty(v-if='noThreads' :value="true" color="info" icon="info")
      p(v-t="'group_page.no_threads_here'")
      p(v-if='!canViewPrivateContent', v-t="'group_page.private_threads'")
    .discussions-panel__list(v-if="discussions.length")
      section.thread-preview-collection__container
        thread-preview-collection(:threads='discussions', :limit='loader.numRequested')
      v-btn.discussions-panel__show-more(v-if="!loader.exhausted" :disabled="loader.loading" @click='loader.loadMore()', v-t="{ path: 'common.action.show_more' }")
      .lmo-hint-text.discussions-panel__no-more-threads(v-t="{ path: 'group_page.no_more_threads' }", v-if='loader.numLoaded > 0 && loader.exhausted')

  .discussions-panel__content(v-if="fragment")
    v-alert.discussions-panel__list--empty(v-if='!searchResults.length' :value="true" color="info" icon="info")
      p(v-t="'group_page.no_threads_here'")
    v-list(two-line v-for="result in searchResults" :key="result.id")
      v-list-tile.thread-preview.thread-preview__link(:to="urlFor(result)")
        v-list-tile-content
          v-list-tile-title {{result.title}}
          v-list-tile-sub-title
            span(v-html="result.resultGroupName")
            |
            | ·
            |
            time-ago(:date='result.lastActivityAt')
            |
            | ·
            |
            span(v-html="result.blurb")
</template>


修改ie9渐变的问题

修改ie9渐变的问题

<template>

属性信息

      <input type="text" class="info-window-layer-select" disabled="true" :value="currentInfo.currentName">

        {{item.currentName}}

          {{key | getAliasName(fieldsInfo)}}
          {{value}}

</template>


Use warning color vor leave conversation button

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

<template>

			{{ t('spreed', 'Be careful, these actions cannot be undone.') }}

			<template v-if="canLeaveConversation">

{{ t('spreed', 'Leave conversation') }}

					{{ t('spreed', 'Once a conversation is left, to rejoin a closed conversation, an invite is needed. An open conversation can be rejoined at any time.') }}

				<Button type="warning" @click.prevent.exact="leaveConversation">
					{{ t('spreed', 'Leave conversation') }}
				</Button>
			</template>
			<template v-if="canDeleteConversation">

{{ t('spreed', 'Delete conversation') }}

					{{ t('spreed', 'Permanently delete this conversation.') }}

				<Button type="error"
					@click.prevent.exact="deleteConversation">
					{{ t('spreed', 'Delete conversation') }}
				</Button>
			</template>
			<template v-if="canDeleteConversation">

{{ t('spreed', 'Delete chat messages') }}

					{{ t('spreed', 'Permanently delete all the messages in this conversation.') }}

				<Button type="error"
					@click.prevent.exact="clearChatHistory">
					{{ t('spreed', 'Delete chat messages') }}
				</Button>
			</template>

</template>

<template>

			{{ t('spreed', 'Be careful, these actions cannot be undone.') }}

			<template v-if="canLeaveConversation">

{{ t('spreed', 'Leave conversation') }}

					{{ t('spreed', 'Once a conversation is left, to rejoin a closed conversation, an invite is needed. An open conversation can be rejoined at any time.') }}

				<Button type="secondary" @click.prevent.exact="leaveConversation">
					{{ t('spreed', 'Leave conversation') }}
				</Button>
			</template>
			<template v-if="canDeleteConversation">

{{ t('spreed', 'Delete conversation') }}

					{{ t('spreed', 'Permanently delete this conversation.') }}

				<Button type="error"
					@click.prevent.exact="deleteConversation">
					{{ t('spreed', 'Delete conversation') }}
				</Button>
			</template>
			<template v-if="canDeleteConversation">

{{ t('spreed', 'Delete chat messages') }}

					{{ t('spreed', 'Permanently delete all the messages in this conversation.') }}

				<Button type="error"
					@click.prevent.exact="clearChatHistory">
					{{ t('spreed', 'Delete chat messages') }}
				</Button>
			</template>

</template>


fix: 解决picker组件关闭时内部状态未重置的问题 #192

fix: 解决picker组件关闭时内部状态未重置的问题 #192

<template>
  <uni-picker @touchmove.prevent>
    <transition name="uni-fade">

    </transition>

取消

确定

      <v-uni-picker-view
        v-if="visible"
        :value.sync="valueArray"
        class="uni-picker-content">
        <v-uni-picker-view-column
          v-for="(range,index0) in rangeArray"
          :key="index0">

{{ typeof item==='object'?item[rangeKey]||'':item }}{{ units[index0]||'' }}

        </v-uni-picker-view-column>
      </v-uni-picker-view>

  </uni-picker>
</template>


Remove test buttons

Remove test buttons

<template>

    <v-container>
      <BlockTitle :data="titleData" class="mb-10" />
      <v-sheet
        max-width="800px"
        class="mx-auto border-radius--10px"
        color="white"
      >

              STEP 1. Download & Install

              STEP 2. Create a new wallet

              STEP 3. Well done

            <v-tabs-items v-model="tab">
              <v-tab-item :value="'tab-1'">
                <Step1 />
              </v-tab-item>
              <v-tab-item :value="'tab-2'">
                <Step2 />
              </v-tab-item>
              <v-tab-item :value="'tab-3'">
                <Step3 />
              </v-tab-item>
            </v-tabs-items>

      </v-sheet>
    </v-container>

</template>

<template>

    <v-container>
      <v-btn small @click="tab = 'tab-1'">1</v-btn>
      <v-btn small @click="tab = 'tab-2'">2</v-btn>
      <v-btn small @click="tab = 'tab-3'">3</v-btn>
      <BlockTitle :data="titleData" class="mb-10" />
      <v-sheet
        max-width="800px"
        class="mx-auto border-radius--10px"
        color="white"
      >

              STEP 1. Download & Install

              STEP 2. Create a new wallet

              STEP 3. Well done

            <v-tabs-items v-model="tab">
              <v-tab-item :value="'tab-1'">
                <Step1 />
              </v-tab-item>
              <v-tab-item :value="'tab-2'">
                <Step2 />
              </v-tab-item>
              <v-tab-item :value="'tab-3'">
                <Step3 />
              </v-tab-item>
            </v-tabs-items>

      </v-sheet>
    </v-container>

</template>


[Client] Resolve #3662

[Client] Resolve #3662

<template>
<time class="mk-time">
	{{ relative }}
	{{ absolute }}
	{{ absolute }} ({{ relative }})
</time>
</template>


scrollSegment组件debug

scrollSegment组件debug

<template>

                <slot></slot>

</template>

<template>

                <slot></slot>

</template>


Fixed typo

Fixed typo

<template>
  <v-ons-dialog cancelable
    :visible="dialogVisible"
    @prehide="closeTweetItemDialog"
  >
    <v-ons-list>
      <v-ons-list-item
        tappable
      >
        <v-ons-icon icon="ion-reply" class="icon"></v-ons-icon>
        Reply
      </v-ons-list-item>
      <v-ons-list-item
        tappable
      >
        <v-ons-icon icon="ion-heart" class="icon"></v-ons-icon>
        {{ favoritePrefix }} favorite
      </v-ons-list-item>
    </v-ons-list>
  </v-ons-dialog>
</template>

<script>
  import { mapState, mapActions } from 'vuex'

  export default {
    name: 'item-dialog',
    computed: {
      favoritePrefix: function () {
        return this.favorited ? 'Add' : 'Remove'
      },
      ...mapState({
        dialogVisible: state => state.app.tweetItemDialogVisible,
        favorited: state => state.app.favorited
      })
    },
    methods: mapActions([
      'closeTweetItemDialog'
    ])
  }
</script>

<style scoped>
  .icon {
    margin-right: 5px;
  }
</style>

<template>
  <v-ons-dialog cancelable
    :visible="dialogVisible"
    @prehide="closeTweetItemDialog"
  >
    <v-ons-list>
      <v-ons-list-item
        tappable
      >
        <v-ons-icon icon="ion-reply" class="icon"></v-ons-icon>
        Reply
      </v-ons-list-item>
      <v-ons-list-item
        tappable
      >
        <v-ons-icon icon="ion-heart" class="icon"></v-ons-icon>
        {{ favotitePrefix }} favorite
      </v-ons-list-item>
    </v-ons-list>
  </v-ons-dialog>
</template>

<script>
  import { mapState, mapActions } from 'vuex'

  export default {
    name: 'item-dialog',
    computed: {
      favotitePrefix: function () {
        return this.favotited ? 'Add' : 'Remove'
      },
      ...mapState({
        dialogVisible: state => state.app.tweetItemDialogVisible,
        favotited: state => state.app.favotited
      })
    },
    methods: mapActions([
      'closeTweetItemDialog'
    ])
  }
</script>

<style scoped>
  .icon {
    margin-right: 5px;
  }
</style>


Fix issue conversation

Fix issue conversation

Since allowing messages it now needs to pass content/images fields
in an object...

<template>
  <div
    v-if="conversation && !isClosed && !conversation.canFetchFuture"
    class="bg-white"
  >
    <ConversationCompose
      :status="conversation.sendStatus"
      :placeholder="messagePrompt"
      :is-participant="conversation.isParticipant"
      slim
      filled
      square
      @submit="sendMessage"
    />
  </div>
</template>

<script>
import ConversationCompose from '@/messages/components/ConversationCompose'

import { mapActions } from 'vuex'

export default {
  components: {
    ConversationCompose,
  },
  props: {
    conversation: {
      type: Object,
      default: null,
    },
  },
  computed: {
    messagePrompt () {
      if (!this.conversation) return
      if (this.conversation.thread) {
        return this.$t('CONVERSATION.REPLY_TO_MESSAGE')
      }
      if (this.conversation.messages.length > 0) {
        return this.$t('WALL.WRITE_MESSAGE')
      }
      return this.$t('WALL.WRITE_FIRST_MESSAGE')
    },
    isClosed () {
      return this.conversation && this.conversation.isClosed
    },
  },
  methods: {
    ...mapActions({
      send: 'conversations/send',
    }),
    sendMessage ({ content, images }) {
      const data = this.conversation.thread
        ? {
          id: this.conversation.conversation,
          threadId: this.conversation.id,
          content,
          images,
        }
        : {
          id: this.conversation.id,
          content,
          images,
        }
      this.send(data)
    },
  },

}
</script>

<template>
  <div
    v-if="conversation && !isClosed && !conversation.canFetchFuture"
    class="bg-white"
  >
    <ConversationCompose
      :status="conversation.sendStatus"
      :placeholder="messagePrompt"
      :is-participant="conversation.isParticipant"
      slim
      filled
      square
      @submit="sendMessage"
    />
  </div>
</template>

<script>
import ConversationCompose from '@/messages/components/ConversationCompose'

import { mapActions } from 'vuex'

export default {
  components: {
    ConversationCompose,
  },
  props: {
    conversation: {
      type: Object,
      default: null,
    },
  },
  computed: {
    messagePrompt () {
      if (!this.conversation) return
      if (this.conversation.thread) {
        return this.$t('CONVERSATION.REPLY_TO_MESSAGE')
      }
      if (this.conversation.messages.length > 0) {
        return this.$t('WALL.WRITE_MESSAGE')
      }
      return this.$t('WALL.WRITE_FIRST_MESSAGE')
    },
    isClosed () {
      return this.conversation && this.conversation.isClosed
    },
  },
  methods: {
    ...mapActions({
      send: 'conversations/send',
    }),
    sendMessage (content) {
      const data = this.conversation.thread
        ? {
          id: this.conversation.conversation,
          threadId: this.conversation.id,
          content,
        }
        : {
          id: this.conversation.id,
          content,
        }
      this.send(data)
    },
  },

}
</script>


feat(docs): update sponsors list

feat(docs): update sponsors list

<template lang="pug">
q-intersection(once)
  .row.items-center.justify-center
    sponsor(img="mio-ab.svg" name="Mio AB" url="https://mio.se/")
    sponsor(img="dreamonkey.png" name="Dreamonkey" url="https://dreamonkey.com/")
    sponsor(img="hapag-lloyd.png" name="Hapag-LLoyd" url="https://www.hapag-lloyd.com/en/company/press/photos-videos/hapag-lloyd-logo.html")
    sponsor(img="campus-cloud-services.png" name="Campus Cloud Services" url="http://campuscloudservices.com/")
    sponsor(img="platform-purple.png" name="Platform Purple" url="https://platformpurple.com/")
    sponsor(img="irewind.png" name="iRewind" url="https://irewind.com/")
    sponsor(img="truelogic.png" name="Truelogic" url="https://truelogic.com/")
    sponsor(img="juggle-street.png" name="Juggle Street" url="https://www.jugglestreet.com.au/")
    sponsor(img="digitalocean.svg" name="Digitalocean" url="https://digitalocean.com/")
    sponsor(img="com-com-services.png" name="Com Com Services" url="http://comcomservices.com/")
    sponsor(img="kalisio.png" name="Kalisio" url="https://kalisio.com/")
    sponsor(img="letsbutterfly.png" name="LetsButterfly" url="https://www.letsbutterfly.com/")
    sponsor(img="project-finance.png" name="Project Finance" url="https://www.projectfinance.io/")
    sponsor(img="ib-langenthal.svg" name="IB Langenthal AG" url="https://ib-langenthal.ch/")
    sponsor(img="debricked.png" name="Debricked AB" url="https://debricked.com/")
    sponsor(img="qintil.png" name="Qintil Technology Ltd" url="https://qintil.com/")
    sponsor(img="synestia.svg" name="Synestia" url="https://synestia.pl/")
</template>

<script>
import Sponsor from 'components/page-parts/sponsors-and-backers/Sponsor'

export default {
  name: 'SponsorList',

  components: {
    Sponsor
  }
}
</script>

<template lang="pug">
q-intersection(once)
  .row.items-center.justify-center
    sponsor(img="mio-ab.svg" name="Mio AB" url="https://mio.se/")
    sponsor(img="dreamonkey.png" name="Dreamonkey" url="https://dreamonkey.com/")
    sponsor(img="campus-cloud-services.png" name="Campus Cloud Services" url="http://campuscloudservices.com/")
    sponsor(img="platform-purple.png" name="Platform Purple" url="https://platformpurple.com/")
    sponsor(img="irewind.png" name="iRewind" url="https://irewind.com/")
    sponsor(img="truelogic.png" name="Truelogic" url="https://truelogic.com/")
    sponsor(img="juggle-street.png" name="Juggle Street" url="https://www.jugglestreet.com.au/")
    sponsor(img="digitalocean.svg" name="Digitalocean" url="https://digitalocean.com/")
    sponsor(img="com-com-services.png" name="Com Com Services" url="http://comcomservices.com/")
    sponsor(img="kalisio.png" name="Kalisio" url="https://kalisio.com/")
    sponsor(img="letsbutterfly.png" name="LetsButterfly" url="https://www.letsbutterfly.com/")
    sponsor(img="project-finance.png" name="Project Finance" url="https://www.projectfinance.io/")
    sponsor(img="ib-langenthal.svg" name="IB Langenthal AG" url="https://ib-langenthal.ch/")
    sponsor(img="debricked.png" name="Debricked AB" url="https://debricked.com/")
    sponsor(img="qintil.png" name="Qintil Technology Ltd" url="https://qintil.com/")
    sponsor(img="synestia.svg" name="Synestia" url="https://synestia.pl/")
</template>

<script>
import Sponsor from 'components/page-parts/sponsors-and-backers/Sponsor'

export default {
  name: 'SponsorList',

  components: {
    Sponsor
  }
}
</script>


Added back header page styles.

Added back header page styles.

<template lang="md" src="./header.md" />
<script>
import View from '@components/View';
import { IButton, IHeader } from '@inkline/inkline';

export default {
    name: 'HeaderView',
    layout: 'documentation',
    extends: View,
    head: {
        title: 'Header'
    },
    components: {
        IButton,
        IHeader
    }
};
</script>
<style lang="stylus">
#cover-inkline-header
    background-image: url('~static/images/background.jpg')
</style>

<template lang="md" src="./header.md" />
<script>
import View from '@components/View';
import { IButton, IHeader } from '@inkline/inkline';

export default {
    name: 'HeaderView',
    layout: 'documentation',
    extends: View,
    head: {
        title: 'Header'
    },
    components: {
        IButton,
        IHeader
    }
};
</script>
<style src="./style.styl" lang="stylus"></style>