perf[Example]: refine example demo

perf[Example]: refine example demo

<template>

    <el-form ref="postForm" :model="postForm" :rules="rules" class="form-container">
      <sticky :z-index="10" :class-name="'sub-navbar '+postForm.status">
        <CommentDropdown v-model="postForm.comment_disabled" />
        <PlatformDropdown v-model="postForm.platforms" />
        <SourceUrlDropdown v-model="postForm.source_uri" />
        <el-button v-loading="loading" style="margin-left: 10px;" type="success" @click="submitForm">
          Publush
        </el-button>
        <el-button v-loading="loading" type="warning" @click="draftForm">
          Draft
        </el-button>
      </sticky>

        <el-row>
          <Warning />

          <el-col :span="24">
            <el-form-item style="margin-bottom: 40px;" prop="title">
              <MDinput v-model="postForm.title" :maxlength="100" name="name" required>
                Title
              </MDinput>
            </el-form-item>

              <el-row>
                <el-col :span="8">
                  <el-form-item label-width="60px" label="Author:" class="postInfo-container-item">
                    <el-select v-model="postForm.author" :remote-method="getRemoteUserList" filterable default-first-option remote placeholder="Search user">
                      <el-option v-for="(item,index) in userListOptions" :key="item+index" :label="item" :value="item" />
                    </el-select>
                  </el-form-item>
                </el-col>

                <el-col :span="10">
                  <el-form-item label-width="120px" label="Publush Time:" class="postInfo-container-item">
                    <el-date-picker v-model="displayTime" type="datetime" format="yyyy-MM-dd HH:mm:ss" placeholder="Select date and time" />
                  </el-form-item>
                </el-col>

                <el-col :span="6">
                  <el-form-item label-width="90px" label="Importance:" class="postInfo-container-item">
                    <el-rate
                      v-model="postForm.importance"
                      :max="3"
                      :colors="['#99A9BF', '#F7BA2A', '#FF9900']"
                      :low-threshold="1"
                      :high-threshold="3"
                      style="margin-top:8px;"
                    />
                  </el-form-item>
                </el-col>
              </el-row>

          </el-col>
        </el-row>

        <el-form-item style="margin-bottom: 40px;" label-width="70px" label="Summary:">
          <el-input v-model="postForm.content_short" :rows="1" type="textarea" class="article-textarea" autosize placeholder="Please enter the content" />
          {{ contentShortLength }}words
        </el-form-item>

        <el-form-item prop="content" style="margin-bottom: 30px;">
          <Tinymce ref="editor" v-model="postForm.content" :height="400" />
        </el-form-item>

        <el-form-item prop="image_uri" style="margin-bottom: 30px;">
          <Upload v-model="postForm.image_uri" />
        </el-form-item>

    </el-form>

</template>

<template>

    <el-form ref="postForm" :model="postForm" :rules="rules" class="form-container">
      <sticky :z-index="10" :class-name="'sub-navbar '+postForm.status">
        <CommentDropdown v-model="postForm.comment_disabled" />
        <PlatformDropdown v-model="postForm.platforms" />
        <SourceUrlDropdown v-model="postForm.source_uri" />
        <el-button v-loading="loading" style="margin-left: 10px;" type="success" @click="submitForm">
          Publush
        </el-button>
        <el-button v-loading="loading" type="warning" @click="draftForm">
          Draft
        </el-button>
      </sticky>

        <el-row>
          <Warning />

          <el-col :span="24">
            <el-form-item style="margin-bottom: 40px;" prop="title">
              <MDinput v-model="postForm.title" :maxlength="100" name="name" required>
                Title
              </MDinput>
            </el-form-item>

              <el-row>
                <el-col :span="8">
                  <el-form-item label-width="60px" label="Author:" class="postInfo-container-item">
                    <el-select v-model="postForm.author" :remote-method="getRemoteUserList" filterable default-first-option remote placeholder="Search user">
                      <el-option v-for="(item,index) in userListOptions" :key="item+index" :label="item" :value="item" />
                    </el-select>
                  </el-form-item>
                </el-col>

                <el-col :span="10">
                  <el-form-item label-width="120px" label="Publush Time:" class="postInfo-container-item">
                    <el-date-picker v-model="postForm.display_time" type="datetime" format="yyyy-MM-dd HH:mm:ss" placeholder="Select date and time" />
                  </el-form-item>
                </el-col>

                <el-col :span="6">
                  <el-form-item label-width="90px" label="Importance:" class="postInfo-container-item">
                    <el-rate
                      v-model="postForm.importance"
                      :max="3"
                      :colors="['#99A9BF', '#F7BA2A', '#FF9900']"
                      :low-threshold="1"
                      :high-threshold="3"
                      style="margin-top:8px;"
                    />
                  </el-form-item>
                </el-col>
              </el-row>

          </el-col>
        </el-row>

        <el-form-item style="margin-bottom: 40px;" label-width="70px" label="Summary:">
          <el-input v-model="postForm.content_short" :rows="1" type="textarea" class="article-textarea" autosize placeholder="Please enter the content" />
          {{ contentShortLength }}words
        </el-form-item>

        <el-form-item prop="content" style="margin-bottom: 30px;">
          <Tinymce ref="editor" v-model="postForm.content" :height="400" />
        </el-form-item>

        <el-form-item prop="image_uri" style="margin-bottom: 30px;">
          <Upload v-model="postForm.image_uri" />
        </el-form-item>

    </el-form>

</template>


fix issue in code editor

fix issue in code editor

<template>
  <the-code-editor-iframe />
</template>

<script lang="ts">
import Vue from 'vue';
import TheCodeEditorIframe from './code/TheCodeEditorIframe.vue';

import {
  IframeMessageType,
  ParentUpdateCssMessage,
} from './../../monaco-editor/types';

export default Vue.extend({
  name: 'TheCodeEditor',

  components: {
    TheCodeEditorIframe,
  },

  computed: {
    css(): string {
      return this.$store.state.css;
    },
  },

  watch: {
    css(value: string): void {
      const iframe = this.$el.querySelector('iframe');

      // only handle the case where the css is deleted by the user
      // from outside of the code editor. else, this will get invoked on every edit.
      if (value === '' && iframe?.contentWindow) {
        this.updateCssInMonacoEditor(iframe?.contentWindow);
      }
    },
  },

  created() {
    this.attachListeners();
  },

  beforeDestroy() {
    this.detachListeners();
  },

  methods: {
    attachListeners(): void {
      window.addEventListener('message', this.handleMessage);
    },

    detachListeners(): void {
      window.removeEventListener('message', this.handleMessage);
    },

    handleMessage(message: {
      source: Window | MessagePort | ServiceWorker | null;
      data: IframeMessageType;
    }): void {
      if (message.data.type === 'stylebotMonacoIframeLoaded') {
        this.updateCssInMonacoEditor(message.source as Window);
      } else if (message.data.type === 'stylebotMonacoIframeCssUpdated') {
        this.$store.dispatch('applyCss', { css: message.data.css });
      }
    },

    updateCssInMonacoEditor(iframeContentWindow: Window): void {
      const message: ParentUpdateCssMessage = {
        css: this.css,
        type: 'stylebotCssUpdate',
      };

      iframeContentWindow.postMessage(message, chrome.runtime.getURL('*'));
    },
  },
});
</script>

<template>
  <the-code-editor-iframe />
</template>

<script lang="ts">
import Vue from 'vue';
import TheCodeEditorIframe from './code/TheCodeEditorIframe.vue';

import {
  IframeMessageType,
  ParentUpdateCssMessage,
} from './../../monaco-editor/types';

export default Vue.extend({
  name: 'TheCodeEditor',

  components: {
    TheCodeEditorIframe,
  },

  computed: {
    css(): string {
      return this.$store.state.css;
    },
  },

  watch: {
    css(): void {
      const iframe = this.$el.querySelector('iframe');

      if (iframe?.contentWindow) {
        this.updateCssInMonacoEditor(iframe?.contentWindow);
      }
    },
  },

  created() {
    this.attachListeners();
  },

  beforeDestroy() {
    this.detachListeners();
  },

  methods: {
    attachListeners(): void {
      window.addEventListener('message', this.handleMessage);
    },

    detachListeners(): void {
      window.removeEventListener('message', this.handleMessage);
    },

    handleMessage(message: {
      source: Window | MessagePort | ServiceWorker | null;
      data: IframeMessageType;
    }): void {
      if (message.data.type === 'stylebotMonacoIframeLoaded') {
        this.updateCssInMonacoEditor(message.source as Window);
      } else if (message.data.type === 'stylebotMonacoIframeCssUpdated') {
        this.$store.dispatch('applyCss', { css: message.data.css });
      }
    },

    updateCssInMonacoEditor(iframeContentWindow: Window): void {
      const message: ParentUpdateCssMessage = {
        css: this.css,
        type: 'stylebotCssUpdate',
      };

      iframeContentWindow.postMessage(message, chrome.runtime.getURL('*'));
    },
  },
});
</script>


Reset TextEditor on step change

Reset TextEditor on step change

ZETA-24

<template>
  <v-card>
    <v-overlay v-if="modelInstances.length" :absolute="true" :opacity="0.85">
      <v-icon x-large>mdi-folder-lock</v-icon>

        Locked because there are model instances

    </v-overlay>

    <v-card-title>
      Edit project {{ gdslProject.name }}
    </v-card-title>

    <v-divider class="ma-0"></v-divider>

    <v-card-text>
      <v-stepper>
        <v-stepper-header>
          <v-stepper-step :complete="!isConceptEmpty" step="1">Concept
Editor</v-stepper-step>
          <v-divider></v-divider>
          <v-stepper-step :complete="!isShapeEmpty" step="2">Shape</v-stepper-step>
          <v-divider></v-divider>
          <v-stepper-step :complete="!isStyleEmpty" step="3">Style</v-stepper-step>
          <v-divider></v-divider>
          <v-stepper-step :complete="!isDiagramEmpty" step="4">Diagram</v-stepper-step>
        </v-stepper-header>
      </v-stepper>

      <v-dialog v-model="editProjectDialog" eager fullscreen hide-overlay transition="dialog-bottom-transition">
        <template v-slot:activator="{ on, attrs }">

            <v-btn v-on:click="onOpenEditorDialog" class="mt-4" color="primary" depressed v-bind="attrs"
                   v-on="on">
              Edit project
            </v-btn>

        </template>

        <v-card>
          <v-toolbar dark color="primary" class="rounded-0">
            <v-btn icon dark @click="closeEditorDialog">
              <v-icon>mdi-close</v-icon>
            </v-btn>
            <v-toolbar-title>Edit project</v-toolbar-title>
            <v-spacer></v-spacer>

            <v-toolbar-items>
              <v-stepper v-model="currentStep" class="elevation-0 rounded-0">
                <v-stepper-header>
                  <v-stepper-step class="stepper-step-hover" @click="setCurrentStep(1)" :complete="isConceptStepCompleted" step="1">
                    {{ step1 }}
                  </v-stepper-step>
                  <v-divider></v-divider>
                  <v-stepper-step class="stepper-step-hover" @click="setCurrentStep(2)" :complete="isShapeStepCompleted" step="2">
                    {{ step2 }}
                  </v-stepper-step>
                  <v-divider></v-divider>
                  <v-stepper-step class="stepper-step-hover" @click="setCurrentStep(3)" :complete="isStyleStepCompleted" step="3">
                    {{ step3 }}
                  </v-stepper-step>
                  <v-divider></v-divider>
                  <v-stepper-step class="stepper-step-hover" @click="setCurrentStep(4)" :complete="isDiagramStepCompleted" step="4">
                    {{ step4 }}
                  </v-stepper-step>
                </v-stepper-header>
              </v-stepper>

              <v-btn :disabled="!allowContinue" text v-on:click="setCurrentStep(++currentStep)">Continue</v-btn>
            </v-toolbar-items>
          </v-toolbar>

          <GraphEditor v-if="isConceptActive" ref="GraphComponent"></GraphEditor>

          <v-container fluid class="py-0">
            <v-row v-show="!isConceptActive">
              <v-col md="3">

              </v-col>

              <v-col md="3" class="code-editor" ref="shapeEditorElement" :data-meta-model-id="gdslProject.id"
                     :data-dsl-type="step2">
                <v-card :disabled="!isShapeActive">
                  <v-card-title>
                    {{ step2 }}
                    <v-spacer></v-spacer>

                      <v-chip class="js-save-successful" style="display: none" color="green" text-color="white">
                        Saving succeed
                      </v-chip>
                      <v-chip class="js-save-failed" style="display: none" color="red" text-color="white">
                        Saving failed
                      </v-chip>
                      <v-btn :disabled="!isShapeActive" class="js-save" icon color="primary">
                        <v-icon>mdi-content-save</v-icon>
                      </v-btn>

                  </v-card-title>

                  <v-card-text>

                  </v-card-text>
                </v-card>
              </v-col>

              <v-col md="3" class="code-editor" ref="styleEditorElement" :data-meta-model-id="gdslProject.id"
                     :data-dsl-type="step3">
                <v-card :disabled="!isStyleActive">
                  <v-card-title>
                    {{ step3 }}
                    <v-spacer></v-spacer>

                      <v-chip class="js-save-successful" style="display: none" color="green" text-color="white">
                        Saving succeed
                      </v-chip>
                      <v-chip class="js-save-failed" style="display: none" color="red" text-color="white">
                        Saving failed
                      </v-chip>
                      <v-btn :disabled="!isStyleActive" class="js-save" icon color="primary">
                        <v-icon>mdi-content-save</v-icon>
                      </v-btn>

                  </v-card-title>

                  <v-card-text>

                  </v-card-text>
                </v-card>
              </v-col>

              <v-col md="3" class="code-editor" ref="diagramEditorElement" :data-meta-model-id="gdslProject.id"
                     :data-dsl-type="step4">
                <v-card :disabled="!isDiagramActive">
                  <v-card-title>
                    {{ step4 }}
                    <v-spacer></v-spacer>

                      <v-chip class="js-save-successful" style="display: none" color="green" text-color="white">
                        Saving succeed
                      </v-chip>
                      <v-chip class="js-save-failed" style="display: none" color="red" text-color="white">
                        Saving failed
                      </v-chip>
                      <v-btn :disabled="!isDiagramActive" class="js-save" icon color="primary">
                        <v-icon>mdi-content-save</v-icon>
                      </v-btn>

                  </v-card-title>

                  <v-card-text>

                  </v-card-text>
                </v-card>
              </v-col>
            </v-row>
          </v-container>
        </v-card>
      </v-dialog>
    </v-card-text>

    <v-divider class="ma-0"></v-divider>

    <v-card-actions>
      <v-menu offset-y>
        <template v-slot:activator="{ on }">
          <v-btn color="secondary" outlined v-on="on">
            Validator
            <v-icon class="ml-1">mdi-chevron-down</v-icon>
          </v-btn>
        </template>
        <v-list>
          <v-list-item v-on:click="validatorGenerate" id="validatorGenerate">
            <v-list-item-title>Generate / Update Validator</v-list-item-title>
          </v-list-item>
          <v-list-item v-on:click="validatorShow" id="validatorShow">
            <v-list-item-title>Show Validation Rules</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
    </v-card-actions>
  </v-card>
</template>


margin to TorrentProgress component

margin to TorrentProgress component

<template>

      {{ title }}

      {{ hash }}

    <md-progress :md-progress="progress"></md-progress>

</template>


fix: sidebar base64 decode (#1975)

fix: sidebar base64 decode (#1975)

<template lang="pug">
  v-app(v-scroll='upBtnScroll', :dark='$vuetify.theme.dark', :class='$vuetify.rtl ? `is-rtl` : `is-ltr`')
    nav-header
    v-navigation-drawer(
      v-if='navMode !== `NONE`'
      :class='$vuetify.theme.dark ? `grey darken-4-d4` : `primary`'
      dark
      app
      clipped
      mobile-break-point='600'
      :temporary='$vuetify.breakpoint.smAndDown'
      v-model='navShown'
      :right='$vuetify.rtl'
      )
      vue-scroll(:ops='scrollStyle')
        nav-sidebar(:color='$vuetify.theme.dark ? `grey darken-4-d4` : `primary`', :items='sidebarDecoded', :nav-mode='navMode')

    v-fab-transition(v-if='navMode !== `NONE`')
      v-btn(
        fab
        color='primary'
        fixed
        bottom
        :right='$vuetify.rtl'
        :left='!$vuetify.rtl'
        small
        @click='navShown = !navShown'
        v-if='$vuetify.breakpoint.mdAndDown'
        v-show='!navShown'
        )
        v-icon mdi-menu

    v-content(ref='content')
      template(v-if='path !== `home`')
        v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-d3` : `grey lighten-3`', flat, dense, v-if='$vuetify.breakpoint.smAndUp')
          //- v-btn.pl-0(v-if='$vuetify.breakpoint.xsOnly', flat, @click='toggleNavigation')
          //-   v-icon(color='grey darken-2', left) menu
          //-   span Navigation
          v-breadcrumbs.breadcrumbs-nav.pl-0(
            :items='breadcrumbs'
            divider='/'
            )
            template(slot='item', slot-scope='props')
              v-icon(v-if='props.item.path === "/"', small, @click='goHome') mdi-home
              v-btn.ma-0(v-else, :href='props.item.path', small, text) {{props.item.name}}
          template(v-if='!isPublished')
            v-spacer
            .caption.red--text {{$t('common:page.unpublished')}}
            status-indicator.ml-3(negative, pulse)
        v-divider
      v-container.grey.pa-0(fluid, :class='$vuetify.theme.dark ? `darken-4-l3` : `lighten-4`')
        v-row(no-gutters, align-content='center', style='height: 90px;')
          v-col.page-col-content.is-page-header(offset-xl='2', offset-lg='3', style='margin-top: auto; margin-bottom: auto;', :class='$vuetify.rtl ? `pr-4` : `pl-4`')
            .headline.grey--text(:class='$vuetify.theme.dark ? `text--lighten-2` : `text--darken-3`') {{title}}
            .caption.grey--text.text--darken-1 {{description}}
      v-divider
      v-container.pl-5.pt-4(fluid, grid-list-xl)
        v-layout(row)
          v-flex.page-col-sd(lg3, xl2, v-if='$vuetify.breakpoint.lgAndUp')
            v-card.mb-5(v-if='tocDecoded.length')
              .overline.pa-5.pb-0(:class='$vuetify.theme.dark ? `blue--text text--lighten-2` : `primary--text`') {{$t('common:page.toc')}}
              v-list.pb-3(dense, nav, :class='$vuetify.theme.dark ? `darken-3-d3` : ``')
                template(v-for='(tocItem, tocIdx) in tocDecoded')
                  v-list-item(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)')
                    v-icon(color='grey', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
                    v-list-item-title.px-3 {{tocItem.title}}
                  //- v-divider(v-if='tocIdx < toc.length - 1 || tocItem.children.length')
                  template(v-for='tocSubItem in tocItem.children')
                    v-list-item(@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)')
                      v-icon.px-3(color='grey lighten-1', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
                      v-list-item-title.px-3.caption.grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-1`') {{tocSubItem.title}}
                    //- v-divider(inset, v-if='tocIdx < toc.length - 1')

            v-card.mb-5(v-if='tags.length > 0')
              .pa-5
                .overline.teal--text.pb-2(:class='$vuetify.theme.dark ? `text--lighten-3` : ``') Tags
                v-chip.mr-1.mb-1(
                  label
                  :color='$vuetify.theme.dark ? `teal darken-1` : `teal lighten-5`'
                  v-for='(tag, idx) in tags'
                  :href='`/t/` + tag.tag'
                  :key='`tag-` + tag.tag'
                  )
                  v-icon(:color='$vuetify.theme.dark ? `teal lighten-3` : `teal`', left, small) mdi-tag
                  span(:class='$vuetify.theme.dark ? `teal--text text--lighten-5` : `teal--text text--darken-2`') {{tag.title}}
                v-chip.mr-1.mb-1(
                  label
                  :color='$vuetify.theme.dark ? `teal darken-1` : `teal lighten-5`'
                  :href='`/t/` + tags.map(t => t.tag).join(`/`)'
                  )
                  v-icon(:color='$vuetify.theme.dark ? `teal lighten-3` : `teal`', size='20') mdi-tag-multiple

            v-card.mb-5(v-if='commentsEnabled && commentsPerms.read')
              .pa-5
                .overline.pb-2.blue-grey--text.d-flex.align-center(:class='$vuetify.theme.dark ? `text--lighten-3` : `text--darken-2`')
                  span Talk
                  //- v-spacer
                  //- v-chip.text-center(
                  //-   v-if='!commentsExternal'
                  //-   label
                  //-   x-small
                  //-   :color='$vuetify.theme.dark ? `blue-grey darken-3` : `blue-grey darken-2`'
                  //-   dark
                  //-   style='min-width: 50px; justify-content: center;'
                  //-   )
                  //-   span {{commentsCount}}
                .d-flex
                  v-btn.text-none(
                    @click='goToComments()'
                    :color='$vuetify.theme.dark ? `blue-grey` : `blue-grey darken-2`'
                    outlined
                    style='flex: 1 1 100%;'
                    small
                    )
                    span.blue-grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-2`') View Discussion
                  v-tooltip(right, v-if='commentsPerms.write')
                    template(v-slot:activator='{ on }')
                      v-btn.ml-2(
                        @click='goToComments(true)'
                        v-on='on'
                        outlined
                        small
                        :color='$vuetify.theme.dark ? `blue-grey` : `blue-grey darken-2`'
                        )
                        v-icon(:color='$vuetify.theme.dark ? `blue-grey lighten-1` : `blue-grey darken-2`', dense) mdi-comment-plus
                    span New Comment

            v-card.mb-5
              .pa-5
                .overline.indigo--text.d-flex(:class='$vuetify.theme.dark ? `text--lighten-3` : ``')
                  span {{$t('common:page.lastEditedBy')}}
                  v-spacer
                  v-tooltip(right, v-if='isAuthenticated')
                    template(v-slot:activator='{ on }')
                      v-btn.btn-animate-edit(icon, :href='"/h/" + locale + "/" + path', v-on='on', x-small)
                        v-icon(color='indigo', dense) mdi-history
                    span {{$t('common:header.history')}}
                .body-2.grey--text(:class='$vuetify.theme.dark ? `` : `text--darken-3`') {{ authorName }}
                .caption.grey--text.text--darken-1 {{ updatedAt | moment('calendar') }}

            //- v-card.mb-5
            //-   .pa-5
            //-     .overline.pb-2.yellow--text(:class='$vuetify.theme.dark ? `text--darken-3` : `text--darken-4`') Rating
            //-     .text-center
            //-       v-rating(
            //-         v-model='rating'
            //-         color='yellow darken-3'
            //-         background-color='grey lighten-1'
            //-         half-increments
            //-         hover
            //-       )
            //-       .caption.grey--text 5 votes

            v-card(flat)
              v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-d3` : `grey lighten-3`', flat, dense)
                v-spacer
                v-tooltip(bottom)
                  template(v-slot:activator='{ on }')
                    v-btn(icon, tile, v-on='on'): v-icon(color='grey') mdi-bookmark
                  span {{$t('common:page.bookmark')}}
                v-menu(offset-y, bottom, min-width='300')
                  template(v-slot:activator='{ on: menu }')
                    v-tooltip(bottom)
                      template(v-slot:activator='{ on: tooltip }')
                        v-btn(icon, tile, v-on='{ ...menu, ...tooltip }'): v-icon(color='grey') mdi-share-variant
                      span {{$t('common:page.share')}}
                  social-sharing(
                    :url='pageUrl'
                    :title='title'
                    :description='description'
                  )
                v-tooltip(bottom)
                  template(v-slot:activator='{ on }')
                    v-btn(icon, tile, v-on='on', @click='print'): v-icon(color='grey') mdi-printer
                  span {{$t('common:page.printFormat')}}
                v-spacer

          v-flex.page-col-content(xs12, lg9, xl10)
            v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='isAuthenticated')
              template(v-slot:activator='{ on: onEditActivator }')
                v-speed-dial(
                  v-model='pageEditFab'
                  direction='top'
                  open-on-hover
                  transition='scale-transition'
                  bottom
                  :right='!$vuetify.rtl'
                  :left='$vuetify.rtl'
                  fixed
                  dark
                  )
                  template(v-slot:activator)
                    v-btn.btn-animate-edit(
                      fab
                      color='primary'
                      v-model='pageEditFab'
                      @click='pageEdit'
                      v-on='onEditActivator'
                      )
                      v-icon mdi-pencil
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageHistory'
                        )
                        v-icon(size='20') mdi-history
                    span {{$t('common:header.history')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageSource'
                        )
                        v-icon(size='20') mdi-code-tags
                    span {{$t('common:header.viewSource')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageDuplicate'
                        )
                        v-icon(size='20') mdi-content-duplicate
                    span {{$t('common:header.duplicate')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageMove'
                        )
                        v-icon(size='20') mdi-content-save-move-outline
                    span {{$t('common:header.move')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        dark
                        small
                        color='red'
                        v-on='on'
                        @click='pageDelete'
                        )
                        v-icon(size='20') mdi-trash-can-outline
                    span {{$t('common:header.delete')}}
              span {{$t('common:page.editPage')}}
            .contents(ref='container')
              slot(name='contents')
            .comments-container#discussion(v-if='commentsEnabled && commentsPerms.read')
              .comments-header
                v-icon.mr-2(dark) mdi-comment-text-outline
                span Comments
              .comments-main
                slot(name='comments')
    nav-footer
    notify
    search-results
    v-fab-transition
      v-btn(
        v-if='upBtnShown'
        fab
        fixed
        bottom
        :right='$vuetify.rtl'
        :left='!$vuetify.rtl'
        small
        :depressed='this.$vuetify.breakpoint.mdAndUp'
        @click='$vuetify.goTo(0, scrollOpts)'
        color='primary'
        dark
        :style='upBtnPosition'
        )
        v-icon mdi-arrow-up
</template>

<script>
import { StatusIndicator } from 'vue-status-indicator'
import Tabset from './tabset.vue'
import NavSidebar from './nav-sidebar.vue'
import Prism from 'prismjs'
import mermaid from 'mermaid'
import { get } from 'vuex-pathify'
import _ from 'lodash'
import ClipboardJS from 'clipboard'
import Vue from 'vue'

Vue.component('tabset', Tabset)

Prism.plugins.autoloader.languages_path = '/_assets/js/prism/'
Prism.plugins.NormalizeWhitespace.setDefaults({
  'remove-trailing': true,
  'remove-indent': true,
  'left-trim': true,
  'right-trim': true,
  'remove-initial-line-feed': true,
  'tabs-to-spaces': 2
})
Prism.plugins.toolbar.registerButton('copy-to-clipboard', (env) => {
  let linkCopy = document.createElement('button')
  linkCopy.textContent = 'Copy'

  const clip = new ClipboardJS(linkCopy, {
    text: () => { return env.code }
  })

  clip.on('success', () => {
    linkCopy.textContent = 'Copied!'
    resetClipboardText()
  })
  clip.on('error', () => {
    linkCopy.textContent = 'Press Ctrl+C to copy'
    resetClipboardText()
  })

  return linkCopy

  function resetClipboardText() {
    setTimeout(() => {
      linkCopy.textContent = 'Copy'
    }, 5000)
  }
})

export default {
  components: {
    NavSidebar,
    StatusIndicator
  },
  props: {
    pageId: {
      type: Number,
      default: 0
    },
    locale: {
      type: String,
      default: 'en'
    },
    path: {
      type: String,
      default: 'home'
    },
    title: {
      type: String,
      default: 'Untitled Page'
    },
    description: {
      type: String,
      default: ''
    },
    createdAt: {
      type: String,
      default: ''
    },
    updatedAt: {
      type: String,
      default: ''
    },
    tags: {
      type: Array,
      default: () => ([])
    },
    authorName: {
      type: String,
      default: 'Unknown'
    },
    authorId: {
      type: Number,
      default: 0
    },
    isPublished: {
      type: Boolean,
      default: false
    },
    toc: {
      type: String,
      default: ''
    },
    sidebar: {
      type: String,
      default: ''
    },
    navMode: {
      type: String,
      default: 'MIXED'
    },
    commentsEnabled: {
      type: Boolean,
      default: false
    },
    commentsPermissions: {
      type: String,
      default: ''
    },
    commentsExternal: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      navShown: false,
      navExpanded: false,
      upBtnShown: false,
      pageEditFab: false,
      scrollOpts: {
        duration: 1500,
        offset: 0,
        easing: 'easeInOutCubic'
      },
      scrollStyle: {
        vuescroll: {},
        scrollPanel: {
          initialScrollX: 0.01, // fix scrollbar not disappearing on load
          scrollingX: false,
          speed: 50
        },
        rail: {
          gutterOfEnds: '2px'
        },
        bar: {
          onlyShowBarOnScroll: false,
          background: '#42A5F5',
          hoverStyle: {
            background: '#64B5F6'
          }
        }
      },
      winWidth: 0
    }
  },
  computed: {
    isAuthenticated: get('user/authenticated'),
    commentsCount: get('page/commentsCount'),
    commentsPerms: get('page/commentsPermissions'),
    rating: {
      get () {
        return 3.5
      },
      set (val) {

      }
    },
    breadcrumbs() {
      return [{ path: '/', name: 'Home' }].concat(_.reduce(this.path.split('/'), (result, value, key) => {
        result.push({
          path: _.get(_.last(result), 'path', `/${this.locale}`) + `/${value}`,
          name: value
        })
        return result
      }, []))
    },
    pageUrl () { return window.location.href },
    upBtnPosition () {
      if (this.$vuetify.breakpoint.mdAndUp) {
        return this.$vuetify.rtl ? `right: 235px;` : `left: 235px;`
      } else {
        return this.$vuetify.rtl ? `right: 65px;` : `left: 65px;`
      }
    },
    sidebarDecoded () {
      return JSON.parse(Buffer.from(this.sidebar, 'base64').toString())
    },
    tocDecoded () {
      return JSON.parse(Buffer.from(this.toc, 'base64').toString())
    }
  },
  created() {
    this.$store.set('page/authorId', this.authorId)
    this.$store.set('page/authorName', this.authorName)
    this.$store.set('page/createdAt', this.createdAt)
    this.$store.set('page/description', this.description)
    this.$store.set('page/isPublished', this.isPublished)
    this.$store.set('page/id', this.pageId)
    this.$store.set('page/locale', this.locale)
    this.$store.set('page/path', this.path)
    this.$store.set('page/tags', this.tags)
    this.$store.set('page/title', this.title)
    this.$store.set('page/updatedAt', this.updatedAt)
    if (this.commentsPermissions) {
      this.$store.set('page/commentsPermissions', JSON.parse(atob(this.commentsPermissions)))
    }

    this.$store.set('page/mode', 'view')
  },
  mounted () {
    if (this.$vuetify.theme.dark) {
      this.scrollStyle.bar.background = '#424242'
    }

    // -> Check side navigation visibility
    this.handleSideNavVisibility()
    window.addEventListener('resize', _.debounce(() => {
      this.handleSideNavVisibility()
    }, 500))

    // -> Highlight Code Blocks
    Prism.highlightAllUnder(this.$refs.container)

    // -> Render Mermaid diagrams
    mermaid.mermaidAPI.initialize({
      startOnLoad: true,
      theme: this.$vuetify.theme.dark ? `dark` : `default`
    })

    // -> Handle anchor scrolling
    if (window.location.hash && window.location.hash.length > 1) {
      if (document.readyState === 'complete') {
        this.$nextTick(() => {
          this.$vuetify.goTo(window.location.hash, this.scrollOpts)
        })
      } else {
        window.addEventListener('load', () => {
          this.$vuetify.goTo(window.location.hash, this.scrollOpts)
        })
      }
    }

    // -> Handle anchor links within the page contents
    this.$nextTick(() => {
      this.$refs.container.querySelectorAll(`a[href^="#"], a[href^="${window.location.href.replace(window.location.hash, '')}#"]`).forEach(el => {
        el.onclick = ev => {
          ev.preventDefault()
          ev.stopPropagation()
          this.$vuetify.goTo(decodeURIComponent(ev.target.hash), this.scrollOpts)
        }
      })
    })
  },
  methods: {
    goHome () {
      window.location.assign('/')
    },
    toggleNavigation () {
      this.navOpen = !this.navOpen
    },
    upBtnScroll () {
      const scrollOffset = window.pageYOffset || document.documentElement.scrollTop
      this.upBtnShown = scrollOffset > window.innerHeight * 0.33
    },
    print () {
      window.print()
    },
    pageEdit () {
      this.$root.$emit('pageEdit')
    },
    pageHistory () {
      this.$root.$emit('pageHistory')
    },
    pageSource () {
      this.$root.$emit('pageSource')
    },
    pageDuplicate () {
      this.$root.$emit('pageDuplicate')
    },
    pageMove () {
      this.$root.$emit('pageMove')
    },
    pageDelete () {
      this.$root.$emit('pageDelete')
    },
    handleSideNavVisibility () {
      if (window.innerWidth === this.winWidth) { return }
      this.winWidth = window.innerWidth
      if (this.$vuetify.breakpoint.mdAndUp) {
        this.navShown = true
      } else {
        this.navShown = false
      }
    },
    goToComments (focusNewComment = false) {
      this.$vuetify.goTo('#discussion', this.scrollOpts)
      if (focusNewComment) {
        document.querySelector('#discussion-new').focus()
      }
    }
  }
}
</script>

<style lang="scss">

.breadcrumbs-nav {
  .v-btn {
    min-width: 0;
    &__content {
      text-transform: none;
    }
  }
  .v-breadcrumbs__divider:nth-child(2n) {
    padding: 0 6px;
  }
  .v-breadcrumbs__divider:nth-child(2) {
    padding: 0 6px 0 12px;
  }
}

.page-col-sd {
  margin-top: -90px;
  align-self: flex-start;
  position: sticky;
  top: 64px;
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  -ms-overflow-style: none;
}

.page-col-sd::-webkit-scrollbar {
  display: none;
}

</style>

<template lang="pug">
  v-app(v-scroll='upBtnScroll', :dark='$vuetify.theme.dark', :class='$vuetify.rtl ? `is-rtl` : `is-ltr`')
    nav-header
    v-navigation-drawer(
      v-if='navMode !== `NONE`'
      :class='$vuetify.theme.dark ? `grey darken-4-d4` : `primary`'
      dark
      app
      clipped
      mobile-break-point='600'
      :temporary='$vuetify.breakpoint.smAndDown'
      v-model='navShown'
      :right='$vuetify.rtl'
      )
      vue-scroll(:ops='scrollStyle')
        nav-sidebar(:color='$vuetify.theme.dark ? `grey darken-4-d4` : `primary`', :items='sidebarDecoded', :nav-mode='navMode')

    v-fab-transition(v-if='navMode !== `NONE`')
      v-btn(
        fab
        color='primary'
        fixed
        bottom
        :right='$vuetify.rtl'
        :left='!$vuetify.rtl'
        small
        @click='navShown = !navShown'
        v-if='$vuetify.breakpoint.mdAndDown'
        v-show='!navShown'
        )
        v-icon mdi-menu

    v-content(ref='content')
      template(v-if='path !== `home`')
        v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-d3` : `grey lighten-3`', flat, dense, v-if='$vuetify.breakpoint.smAndUp')
          //- v-btn.pl-0(v-if='$vuetify.breakpoint.xsOnly', flat, @click='toggleNavigation')
          //-   v-icon(color='grey darken-2', left) menu
          //-   span Navigation
          v-breadcrumbs.breadcrumbs-nav.pl-0(
            :items='breadcrumbs'
            divider='/'
            )
            template(slot='item', slot-scope='props')
              v-icon(v-if='props.item.path === "/"', small, @click='goHome') mdi-home
              v-btn.ma-0(v-else, :href='props.item.path', small, text) {{props.item.name}}
          template(v-if='!isPublished')
            v-spacer
            .caption.red--text {{$t('common:page.unpublished')}}
            status-indicator.ml-3(negative, pulse)
        v-divider
      v-container.grey.pa-0(fluid, :class='$vuetify.theme.dark ? `darken-4-l3` : `lighten-4`')
        v-row(no-gutters, align-content='center', style='height: 90px;')
          v-col.page-col-content.is-page-header(offset-xl='2', offset-lg='3', style='margin-top: auto; margin-bottom: auto;', :class='$vuetify.rtl ? `pr-4` : `pl-4`')
            .headline.grey--text(:class='$vuetify.theme.dark ? `text--lighten-2` : `text--darken-3`') {{title}}
            .caption.grey--text.text--darken-1 {{description}}
      v-divider
      v-container.pl-5.pt-4(fluid, grid-list-xl)
        v-layout(row)
          v-flex.page-col-sd(lg3, xl2, v-if='$vuetify.breakpoint.lgAndUp')
            v-card.mb-5(v-if='tocDecoded.length')
              .overline.pa-5.pb-0(:class='$vuetify.theme.dark ? `blue--text text--lighten-2` : `primary--text`') {{$t('common:page.toc')}}
              v-list.pb-3(dense, nav, :class='$vuetify.theme.dark ? `darken-3-d3` : ``')
                template(v-for='(tocItem, tocIdx) in tocDecoded')
                  v-list-item(@click='$vuetify.goTo(tocItem.anchor, scrollOpts)')
                    v-icon(color='grey', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
                    v-list-item-title.px-3 {{tocItem.title}}
                  //- v-divider(v-if='tocIdx < toc.length - 1 || tocItem.children.length')
                  template(v-for='tocSubItem in tocItem.children')
                    v-list-item(@click='$vuetify.goTo(tocSubItem.anchor, scrollOpts)')
                      v-icon.px-3(color='grey lighten-1', small) {{ $vuetify.rtl ? `mdi-chevron-left` : `mdi-chevron-right` }}
                      v-list-item-title.px-3.caption.grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-1`') {{tocSubItem.title}}
                    //- v-divider(inset, v-if='tocIdx < toc.length - 1')

            v-card.mb-5(v-if='tags.length > 0')
              .pa-5
                .overline.teal--text.pb-2(:class='$vuetify.theme.dark ? `text--lighten-3` : ``') Tags
                v-chip.mr-1.mb-1(
                  label
                  :color='$vuetify.theme.dark ? `teal darken-1` : `teal lighten-5`'
                  v-for='(tag, idx) in tags'
                  :href='`/t/` + tag.tag'
                  :key='`tag-` + tag.tag'
                  )
                  v-icon(:color='$vuetify.theme.dark ? `teal lighten-3` : `teal`', left, small) mdi-tag
                  span(:class='$vuetify.theme.dark ? `teal--text text--lighten-5` : `teal--text text--darken-2`') {{tag.title}}
                v-chip.mr-1.mb-1(
                  label
                  :color='$vuetify.theme.dark ? `teal darken-1` : `teal lighten-5`'
                  :href='`/t/` + tags.map(t => t.tag).join(`/`)'
                  )
                  v-icon(:color='$vuetify.theme.dark ? `teal lighten-3` : `teal`', size='20') mdi-tag-multiple

            v-card.mb-5(v-if='commentsEnabled && commentsPerms.read')
              .pa-5
                .overline.pb-2.blue-grey--text.d-flex.align-center(:class='$vuetify.theme.dark ? `text--lighten-3` : `text--darken-2`')
                  span Talk
                  //- v-spacer
                  //- v-chip.text-center(
                  //-   v-if='!commentsExternal'
                  //-   label
                  //-   x-small
                  //-   :color='$vuetify.theme.dark ? `blue-grey darken-3` : `blue-grey darken-2`'
                  //-   dark
                  //-   style='min-width: 50px; justify-content: center;'
                  //-   )
                  //-   span {{commentsCount}}
                .d-flex
                  v-btn.text-none(
                    @click='goToComments()'
                    :color='$vuetify.theme.dark ? `blue-grey` : `blue-grey darken-2`'
                    outlined
                    style='flex: 1 1 100%;'
                    small
                    )
                    span.blue-grey--text(:class='$vuetify.theme.dark ? `text--lighten-1` : `text--darken-2`') View Discussion
                  v-tooltip(right, v-if='commentsPerms.write')
                    template(v-slot:activator='{ on }')
                      v-btn.ml-2(
                        @click='goToComments(true)'
                        v-on='on'
                        outlined
                        small
                        :color='$vuetify.theme.dark ? `blue-grey` : `blue-grey darken-2`'
                        )
                        v-icon(:color='$vuetify.theme.dark ? `blue-grey lighten-1` : `blue-grey darken-2`', dense) mdi-comment-plus
                    span New Comment

            v-card.mb-5
              .pa-5
                .overline.indigo--text.d-flex(:class='$vuetify.theme.dark ? `text--lighten-3` : ``')
                  span {{$t('common:page.lastEditedBy')}}
                  v-spacer
                  v-tooltip(right, v-if='isAuthenticated')
                    template(v-slot:activator='{ on }')
                      v-btn.btn-animate-edit(icon, :href='"/h/" + locale + "/" + path', v-on='on', x-small)
                        v-icon(color='indigo', dense) mdi-history
                    span {{$t('common:header.history')}}
                .body-2.grey--text(:class='$vuetify.theme.dark ? `` : `text--darken-3`') {{ authorName }}
                .caption.grey--text.text--darken-1 {{ updatedAt | moment('calendar') }}

            //- v-card.mb-5
            //-   .pa-5
            //-     .overline.pb-2.yellow--text(:class='$vuetify.theme.dark ? `text--darken-3` : `text--darken-4`') Rating
            //-     .text-center
            //-       v-rating(
            //-         v-model='rating'
            //-         color='yellow darken-3'
            //-         background-color='grey lighten-1'
            //-         half-increments
            //-         hover
            //-       )
            //-       .caption.grey--text 5 votes

            v-card(flat)
              v-toolbar(:color='$vuetify.theme.dark ? `grey darken-4-d3` : `grey lighten-3`', flat, dense)
                v-spacer
                v-tooltip(bottom)
                  template(v-slot:activator='{ on }')
                    v-btn(icon, tile, v-on='on'): v-icon(color='grey') mdi-bookmark
                  span {{$t('common:page.bookmark')}}
                v-menu(offset-y, bottom, min-width='300')
                  template(v-slot:activator='{ on: menu }')
                    v-tooltip(bottom)
                      template(v-slot:activator='{ on: tooltip }')
                        v-btn(icon, tile, v-on='{ ...menu, ...tooltip }'): v-icon(color='grey') mdi-share-variant
                      span {{$t('common:page.share')}}
                  social-sharing(
                    :url='pageUrl'
                    :title='title'
                    :description='description'
                  )
                v-tooltip(bottom)
                  template(v-slot:activator='{ on }')
                    v-btn(icon, tile, v-on='on', @click='print'): v-icon(color='grey') mdi-printer
                  span {{$t('common:page.printFormat')}}
                v-spacer

          v-flex.page-col-content(xs12, lg9, xl10)
            v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='isAuthenticated')
              template(v-slot:activator='{ on: onEditActivator }')
                v-speed-dial(
                  v-model='pageEditFab'
                  direction='top'
                  open-on-hover
                  transition='scale-transition'
                  bottom
                  :right='!$vuetify.rtl'
                  :left='$vuetify.rtl'
                  fixed
                  dark
                  )
                  template(v-slot:activator)
                    v-btn.btn-animate-edit(
                      fab
                      color='primary'
                      v-model='pageEditFab'
                      @click='pageEdit'
                      v-on='onEditActivator'
                      )
                      v-icon mdi-pencil
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageHistory'
                        )
                        v-icon(size='20') mdi-history
                    span {{$t('common:header.history')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageSource'
                        )
                        v-icon(size='20') mdi-code-tags
                    span {{$t('common:header.viewSource')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageDuplicate'
                        )
                        v-icon(size='20') mdi-content-duplicate
                    span {{$t('common:header.duplicate')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        small
                        color='white'
                        light
                        v-on='on'
                        @click='pageMove'
                        )
                        v-icon(size='20') mdi-content-save-move-outline
                    span {{$t('common:header.move')}}
                  v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl')
                    template(v-slot:activator='{ on }')
                      v-btn(
                        fab
                        dark
                        small
                        color='red'
                        v-on='on'
                        @click='pageDelete'
                        )
                        v-icon(size='20') mdi-trash-can-outline
                    span {{$t('common:header.delete')}}
              span {{$t('common:page.editPage')}}
            .contents(ref='container')
              slot(name='contents')
            .comments-container#discussion(v-if='commentsEnabled && commentsPerms.read')
              .comments-header
                v-icon.mr-2(dark) mdi-comment-text-outline
                span Comments
              .comments-main
                slot(name='comments')
    nav-footer
    notify
    search-results
    v-fab-transition
      v-btn(
        v-if='upBtnShown'
        fab
        fixed
        bottom
        :right='$vuetify.rtl'
        :left='!$vuetify.rtl'
        small
        :depressed='this.$vuetify.breakpoint.mdAndUp'
        @click='$vuetify.goTo(0, scrollOpts)'
        color='primary'
        dark
        :style='upBtnPosition'
        )
        v-icon mdi-arrow-up
</template>

<script>
import { StatusIndicator } from 'vue-status-indicator'
import Tabset from './tabset.vue'
import NavSidebar from './nav-sidebar.vue'
import Prism from 'prismjs'
import mermaid from 'mermaid'
import { get } from 'vuex-pathify'
import _ from 'lodash'
import ClipboardJS from 'clipboard'
import Vue from 'vue'

Vue.component('tabset', Tabset)

Prism.plugins.autoloader.languages_path = '/_assets/js/prism/'
Prism.plugins.NormalizeWhitespace.setDefaults({
  'remove-trailing': true,
  'remove-indent': true,
  'left-trim': true,
  'right-trim': true,
  'remove-initial-line-feed': true,
  'tabs-to-spaces': 2
})
Prism.plugins.toolbar.registerButton('copy-to-clipboard', (env) => {
  let linkCopy = document.createElement('button')
  linkCopy.textContent = 'Copy'

  const clip = new ClipboardJS(linkCopy, {
    text: () => { return env.code }
  })

  clip.on('success', () => {
    linkCopy.textContent = 'Copied!'
    resetClipboardText()
  })
  clip.on('error', () => {
    linkCopy.textContent = 'Press Ctrl+C to copy'
    resetClipboardText()
  })

  return linkCopy

  function resetClipboardText() {
    setTimeout(() => {
      linkCopy.textContent = 'Copy'
    }, 5000)
  }
})

export default {
  components: {
    NavSidebar,
    StatusIndicator
  },
  props: {
    pageId: {
      type: Number,
      default: 0
    },
    locale: {
      type: String,
      default: 'en'
    },
    path: {
      type: String,
      default: 'home'
    },
    title: {
      type: String,
      default: 'Untitled Page'
    },
    description: {
      type: String,
      default: ''
    },
    createdAt: {
      type: String,
      default: ''
    },
    updatedAt: {
      type: String,
      default: ''
    },
    tags: {
      type: Array,
      default: () => ([])
    },
    authorName: {
      type: String,
      default: 'Unknown'
    },
    authorId: {
      type: Number,
      default: 0
    },
    isPublished: {
      type: Boolean,
      default: false
    },
    toc: {
      type: String,
      default: ''
    },
    sidebar: {
      type: String,
      default: ''
    },
    navMode: {
      type: String,
      default: 'MIXED'
    },
    commentsEnabled: {
      type: Boolean,
      default: false
    },
    commentsPermissions: {
      type: String,
      default: ''
    },
    commentsExternal: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      navShown: false,
      navExpanded: false,
      upBtnShown: false,
      pageEditFab: false,
      scrollOpts: {
        duration: 1500,
        offset: 0,
        easing: 'easeInOutCubic'
      },
      scrollStyle: {
        vuescroll: {},
        scrollPanel: {
          initialScrollX: 0.01, // fix scrollbar not disappearing on load
          scrollingX: false,
          speed: 50
        },
        rail: {
          gutterOfEnds: '2px'
        },
        bar: {
          onlyShowBarOnScroll: false,
          background: '#42A5F5',
          hoverStyle: {
            background: '#64B5F6'
          }
        }
      },
      winWidth: 0
    }
  },
  computed: {
    isAuthenticated: get('user/authenticated'),
    commentsCount: get('page/commentsCount'),
    commentsPerms: get('page/commentsPermissions'),
    rating: {
      get () {
        return 3.5
      },
      set (val) {

      }
    },
    breadcrumbs() {
      return [{ path: '/', name: 'Home' }].concat(_.reduce(this.path.split('/'), (result, value, key) => {
        result.push({
          path: _.get(_.last(result), 'path', `/${this.locale}`) + `/${value}`,
          name: value
        })
        return result
      }, []))
    },
    pageUrl () { return window.location.href },
    upBtnPosition () {
      if (this.$vuetify.breakpoint.mdAndUp) {
        return this.$vuetify.rtl ? `right: 235px;` : `left: 235px;`
      } else {
        return this.$vuetify.rtl ? `right: 65px;` : `left: 65px;`
      }
    },
    sidebarDecoded () {
      return JSON.parse(atob(this.sidebar))
    },
    tocDecoded () {
      return JSON.parse(atob(this.toc))
    }
  },
  created() {
    this.$store.set('page/authorId', this.authorId)
    this.$store.set('page/authorName', this.authorName)
    this.$store.set('page/createdAt', this.createdAt)
    this.$store.set('page/description', this.description)
    this.$store.set('page/isPublished', this.isPublished)
    this.$store.set('page/id', this.pageId)
    this.$store.set('page/locale', this.locale)
    this.$store.set('page/path', this.path)
    this.$store.set('page/tags', this.tags)
    this.$store.set('page/title', this.title)
    this.$store.set('page/updatedAt', this.updatedAt)
    if (this.commentsPermissions) {
      this.$store.set('page/commentsPermissions', JSON.parse(atob(this.commentsPermissions)))
    }

    this.$store.set('page/mode', 'view')
  },
  mounted () {
    if (this.$vuetify.theme.dark) {
      this.scrollStyle.bar.background = '#424242'
    }

    // -> Check side navigation visibility
    this.handleSideNavVisibility()
    window.addEventListener('resize', _.debounce(() => {
      this.handleSideNavVisibility()
    }, 500))

    // -> Highlight Code Blocks
    Prism.highlightAllUnder(this.$refs.container)

    // -> Render Mermaid diagrams
    mermaid.mermaidAPI.initialize({
      startOnLoad: true,
      theme: this.$vuetify.theme.dark ? `dark` : `default`
    })

    // -> Handle anchor scrolling
    if (window.location.hash && window.location.hash.length > 1) {
      if (document.readyState === 'complete') {
        this.$nextTick(() => {
          this.$vuetify.goTo(window.location.hash, this.scrollOpts)
        })
      } else {
        window.addEventListener('load', () => {
          this.$vuetify.goTo(window.location.hash, this.scrollOpts)
        })
      }
    }

    // -> Handle anchor links within the page contents
    this.$nextTick(() => {
      this.$refs.container.querySelectorAll(`a[href^="#"], a[href^="${window.location.href.replace(window.location.hash, '')}#"]`).forEach(el => {
        el.onclick = ev => {
          ev.preventDefault()
          ev.stopPropagation()
          this.$vuetify.goTo(decodeURIComponent(ev.target.hash), this.scrollOpts)
        }
      })
    })
  },
  methods: {
    goHome () {
      window.location.assign('/')
    },
    toggleNavigation () {
      this.navOpen = !this.navOpen
    },
    upBtnScroll () {
      const scrollOffset = window.pageYOffset || document.documentElement.scrollTop
      this.upBtnShown = scrollOffset > window.innerHeight * 0.33
    },
    print () {
      window.print()
    },
    pageEdit () {
      this.$root.$emit('pageEdit')
    },
    pageHistory () {
      this.$root.$emit('pageHistory')
    },
    pageSource () {
      this.$root.$emit('pageSource')
    },
    pageDuplicate () {
      this.$root.$emit('pageDuplicate')
    },
    pageMove () {
      this.$root.$emit('pageMove')
    },
    pageDelete () {
      this.$root.$emit('pageDelete')
    },
    handleSideNavVisibility () {
      if (window.innerWidth === this.winWidth) { return }
      this.winWidth = window.innerWidth
      if (this.$vuetify.breakpoint.mdAndUp) {
        this.navShown = true
      } else {
        this.navShown = false
      }
    },
    goToComments (focusNewComment = false) {
      this.$vuetify.goTo('#discussion', this.scrollOpts)
      if (focusNewComment) {
        document.querySelector('#discussion-new').focus()
      }
    }
  }
}
</script>

<style lang="scss">

.breadcrumbs-nav {
  .v-btn {
    min-width: 0;
    &__content {
      text-transform: none;
    }
  }
  .v-breadcrumbs__divider:nth-child(2n) {
    padding: 0 6px;
  }
  .v-breadcrumbs__divider:nth-child(2) {
    padding: 0 6px 0 12px;
  }
}

.page-col-sd {
  margin-top: -90px;
  align-self: flex-start;
  position: sticky;
  top: 64px;
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  -ms-overflow-style: none;
}

.page-col-sd::-webkit-scrollbar {
  display: none;
}

</style>


[前端] 完成Footer

[前端] 完成Footer

<template>
     © 2016 -  Smallpath的小站  - smallpath.me
 Powered by Vue & Koa2

</template>


Added preferences to the profile

Added preferences to the profile

<template>

            <img class="arrow" :src="`${baseUrl}/assets/images/temp/back_arrow.png`"/>Back to your profile

Edit your profile

Resend Verification Mail

If you cant find our account verification mail, resend a verification mail to your mail now.

                <button class="btn-ha" v-on:click="resend_verification_email">Resend Verfication</button>

personal info

                <form v-on:submit.prevent="submit" class="create-account-form">

You are currently affiliated with {{ user.organization }}.

You are not currently affiliated with an organization.

                    <button
                        type="button"
                        class="btn-ha"
                        v-if="!edit_email"
                        v-on:click="edit_email = true">Edit Affiliation</button>
                    <template v-if="edit_email">

                            Which of the following describes you best?*
                            <select class="form-control" id="level" v-model="level">
                                <option value="Professional">Professional</option>
                                <option value="University">University</option>
                                <option value="High School">High school</option>
                            </select>

                            Work or University Email
                            <input class="form-control" type="email" id="personal-email" v-model="email" />

This is used to affiliate you with an organization (based on the email domain). You will need to verify your association before it shows up on your profile. If you are not added to your organization, or it does not exist, let us know at halite@halite.io.

Please email us your high school name at halite@halite.io to be associated with that high school on the leaderboard.

                    </template>

Account info

                        Username (As shown On Github)

                            <input type="text" class="form-control" placeholder="Enter your username" v-model="username" disabled>

                        Which country will you be playing from?
                        <v-select
                            placeholder="(would prefer not to disclose)"
                            label="label"
                            :on-change="change_country"
                            v-model="selected_country"
                            :options="country_options">
                        </v-select>

                        State, Province, or Region
                        <v-select
                            placeholder="(would prefer not to disclose)"
                            v-model="selected_region"
                            label="label"
                            :options="regions">
                        </v-select>

Hackathons

                        Join a Hackathon
                        <input type="text" class="form-control" placeholder="Enter hackathon code" v-model="hackathon_code">

                        {{ error }}

                    Cancel
                    <button type="submit" class="btn-ha">Update Profile</button>

                            To manage your email preferences, click the relevant link at the bottom of any Halite email.

                </form>

</template>

<template>

            <img class="arrow" :src="`${baseUrl}/assets/images/temp/back_arrow.png`"/>Back to your profile

Edit your profile

Resend Verification Mail

If you cant find our account verification mail, resend a verification mail to your mail now.

                <button class="btn-ha" v-on:click="resend_verification_email">Resend Verfication</button>

personal info

                <form v-on:submit.prevent="submit" class="create-account-form">

You are currently affiliated with {{ user.organization }}.

You are not currently affiliated with an organization.

                    <button
                        type="button"
                        class="btn-ha"
                        v-if="!edit_email"
                        v-on:click="edit_email = true">Edit Affiliation</button>
                    <template v-if="edit_email">

                            Which of the following describes you best?*
                            <select class="form-control" id="level" v-model="level">
                                <option value="Professional">Professional</option>
                                <option value="University">University</option>
                                <option value="High School">High school</option>
                            </select>

                            Work or University Email
                            <input class="form-control" type="email" id="personal-email" v-model="email" />

This is used to affiliate you with an organization (based on the email domain). You will need to verify your association before it shows up on your profile. If you are not added to your organization, or it does not exist, let us know at halite@halite.io.

Please email us your high school name at halite@halite.io to be associated with that high school on the leaderboard.

                    </template>

Account info

                        Username (As shown On Github)

                            <input type="text" class="form-control" placeholder="Enter your username" v-model="username" disabled>

                        Which country will you be playing from?
                        <v-select
                            placeholder="(would prefer not to disclose)"
                            label="label"
                            :on-change="change_country"
                            v-model="selected_country"
                            :options="country_options">
                        </v-select>

                        State, Province, or Region
                        <v-select
                            placeholder="(would prefer not to disclose)"
                            v-model="selected_region"
                            label="label"
                            :options="regions">
                        </v-select>

Hackathons

                        Join a Hackathon
                        <input type="text" class="form-control" placeholder="Enter hackathon code" v-model="hackathon_code">

                        {{ error }}

                    Cancel
                    <button type="submit" class="btn-ha">Update Profile</button>

                </form>

</template>


fix: profileDropdown

fix: profileDropdown

<template>
  <va-dropdown v-model="isShown" class="profile-dropdown" stick-to-edges placement="bottom" :offset="[13, 0]">
    <template #anchor>
      <span class="profile-dropdown__anchor">
        <slot />
        <va-icon class="px-2" :name="isShown ? 'angle_up' : 'angle_down'" :color="theme.primary" />
      </span>
    </template>
    <va-dropdown-content class="profile-dropdown__content">
      <va-list-item v-for="option in options" :key="option.name" class="pa-2">
        <router-link :to="{ name: option.redirectTo }" class="profile-dropdown__item">
          {{ t(`user.${option.name}`) }}
        </router-link>
      </va-list-item>
    </va-dropdown-content>
  </va-dropdown>
</template>

<script setup lang="ts">
  import { computed, ref } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { useGlobalConfig } from 'vuestic-ui'

  const { t } = useI18n()

  withDefaults(
    defineProps<{
      options?: { name: string; redirectTo: string }[]
    }>(),
    {
      options: () => [
        {
          name: 'profile',
          redirectTo: '',
        },
        {
          name: 'logout',
          redirectTo: 'login',
        },
      ],
    },
  )

  const isShown = ref(false)

  const theme = computed(() => useGlobalConfig().getGlobalConfig().colors!)
</script>

<style lang="scss" scoped>
  .profile-dropdown {
    cursor: pointer;

    &__anchor {
      display: inline-block;
    }

    &__item {
      display: block;
      color: var(--va-gray);

      &:hover,
      &:active {
        color: var(--va-primary);
      }
    }
  }
</style>

<template>
  <va-dropdown v-model="isShown" class="profile-dropdown" boundary-body position="bottom" :offset="[13, 0]">
    <template #anchor>
      <span class="profile-dropdown__anchor">
        <slot />
        <va-icon class="px-2" :name="isShown ? 'angle_up' : 'angle_down'" :color="theme.primary" />
      </span>
    </template>
    <va-dropdown-content class="profile-dropdown__content">
      <va-list-item v-for="option in options" :key="option.name">
        <router-link :to="{ name: option.redirectTo }" class="profile-dropdown__item">
          {{ t(`user.${option.name}`) }}
        </router-link>
      </va-list-item>
    </va-dropdown-content>
  </va-dropdown>
</template>

<script setup lang="ts">
  import { computed, ref } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { useGlobalConfig } from 'vuestic-ui'

  const { t } = useI18n()

  withDefaults(
    defineProps<{
      options?: { name: string; redirectTo: string }[]
    }>(),
    {
      options: () => [
        {
          name: 'profile',
          redirectTo: '',
        },
        {
          name: 'logout',
          redirectTo: 'login',
        },
      ],
    },
  )

  const isShown = ref(false)

  const theme = computed(() => useGlobalConfig().getGlobalConfig().colors!)
</script>

<style lang="scss" scoped>
  .profile-dropdown {
    cursor: pointer;

    .va-dropdown-popper__anchor {
      display: flex;
      justify-content: flex-end;
    }

    &__content {
      width: 8rem;
    }

    &__item {
      display: block;
      color: var(--va-gray);

      &:hover,
      &:active {
        color: var(--va-primary);
      }
    }

    .va-dropdown__anchor {
      display: inline-block;
    }
  }
</style>


Add slot reference to Timeline component usage in Home page

<template lang="jade">
	AppPage.HomePage(:header="header")
		AppSection.AppSection-even(:title="'Xebia en quelques chiffres'")
			QuelquesChiffres(:statCards="quelquesChiffres", slot="section-content")
        AppSection.AppSection-even(:title="'Xebia en quelques faits'")
            Timeline(:items="timelineItems", slot="section-content")
</template>

<script>
  import AppPage from 'components/AppPage/AppPage';
  import AppSection from 'components/AppSection/AppSection';

  import {mixin as fontLoader} from 'tools/font-loader';
  import {mixin as sizeClassHelper} from 'tools/size-class-helper';

  import {HomePage as settings} from 'settings/components';
  import header from 'assets/data/home/header.json';

  import QuelquesChiffres from 'components/QuelquesChiffres/QuelquesChiffres';
  import quelquesChiffres from 'assets/data/home/quelques-chiffres.json';

  import Timeline from 'components/Timeline/Timeline';
  import timelineItems from 'assets/data/home/timeline-items.json';

  export default {
    name: 'HomePage',
    mixins: [fontLoader, sizeClassHelper],
    data: function() {
      return {
        header,
        quelquesChiffres,
        timelineItems
      };
    },
    created : function() {
      this.loadFont({
        'text': 'light',
        'title': 'regular'
      });
    },
    computed: {
      settings : function() {
        return settings;
      }
    },
    components: {
      AppPage,
      AppSection,
      QuelquesChiffres,
      Timeline
    }
  };
</script>

<style lang="stylus" src="./Home.styl"></style>

<template lang="jade">
	AppPage.HomePage(:header="header")
		AppSection.AppSection-even(:title="'Xebia en quelques chiffres'")
			QuelquesChiffres(:statCards="quelquesChiffres", slot="section-content")
        AppSection.AppSection-even(:title="'Xebia en quelques faits'")
            Timeline(:items="timelineItems")
</template>

<script>
  import AppPage from 'components/AppPage/AppPage';
  import AppSection from 'components/AppSection/AppSection';

  import {mixin as fontLoader} from 'tools/font-loader';
  import {mixin as sizeClassHelper} from 'tools/size-class-helper';

  import {HomePage as settings} from 'settings/components';
  import header from 'assets/data/home/header.json';

  import QuelquesChiffres from 'components/QuelquesChiffres/QuelquesChiffres';
  import quelquesChiffres from 'assets/data/home/quelques-chiffres.json';

  import Timeline from 'components/Timeline/Timeline';
  import timelineItems from 'assets/data/home/timeline-items.json';

  export default {
    name: 'HomePage',
    mixins: [fontLoader, sizeClassHelper],
    data: function() {
      return {
        header,
        quelquesChiffres,
        timelineItems
      };
    },
    created : function() {
      this.loadFont({
        'text': 'light',
        'title': 'regular'
      });
    },
    computed: {
      settings : function() {
        return settings;
      }
    },
    components: {
      AppPage,
      AppSection,
      QuelquesChiffres,
      Timeline
    }
  };
</script>

<style lang="stylus" src="./Home.styl"></style>


remove import of thing we cant import

remove import of thing we cant import

<script lang="coffee">
import Session        from '@/shared/services/session'
import Records        from '@/shared/services/records'
import EventBus       from '@/shared/services/event_bus'
import AbilityService from '@/shared/services/ability_service'
import LmoUrlService  from '@/shared/services/lmo_url_service'

import { submitForm } from '@/shared/helpers/form'
import { eventHeadline, eventTitle, eventPollType } from '@/shared/helpers/helptext'
import { includes, camelCase } from 'lodash'

export default
  props:
    event: Object
    eventWindow: Object

  watch:
    '$route.params.sequence_id': 'updateIsFocused'

  data: ->
    isDisabled: false
    isFocused: @updateIsFocused()
    collapsed: false
    hover: false

  methods:
    updateIsFocused: ->
      @isFocused = parseInt(@$route.params.sequence_id) == @event.sequenceId

    viewed: (viewed) ->
      @event.markAsRead() if viewed

    hasComponent: ->
      includes(threadItemComponents, camelCase(@event.kind))

    debug: ->
      window.Loomio.debug

    camelCase: camelCase

  computed:
    iconSize: -> if @isNested then 32 else 40

    isNested: -> @event.isNested()

    indent: ->
      @$vuetify.breakpoint.smAndUp && @event.isNested() # && @eventWindow.useNesting

    isUnread: ->
      (Session.user().id != @event.actorId) && false # @eventWindow.isUnread(@event)

    headline: ->
      @$t eventHeadline(@event, true ), # useNesting
        author:   @event.actorName() || @$t('common.anonymous')
        username: @event.actorUsername()
        key:      @event.model().key
        title:    eventTitle(@event)
        polltype: @$t(eventPollType(@event)).toLowerCase()

    link: ->
      LmoUrlService.event @event

</script>

<template lang="pug">
div
  .thread-item.thread-item--collapsed(v-if="collapsed")
    v-layout(:id="'sequence-' + event.sequenceId" :class="{'thread-item--indent': indent}")
      .thread-item__avatar.mr-3.mt-2.d-flex(@mouseover="hover = true" @mouseleave="hover = false")
        v-btn(v-if="hover" icon :width="iconSize" :height="iconSize"  @click="collapsed = false")
          v-icon mdi-arrow-expand-vertical
        user-avatar(v-if='!hover && !event.isForkable() && event.actor()' :user='event.actor()' :size='iconSize')
      h3.thread-item__title.body-2.my-1.d-flex.align-center.wrap(:id="'event-' + event.id")
        //- div
          | sid {{event.sequenceId}}
          | pos {{event.position}}
          //- | pid: {{event.model().parentId}}
          | depth: {{event.depth}}
        slot(name="headline")
          span(v-html='headline')
        mid-dot
        router-link.thread-item__link(:to='link')
          time-ago.timeago--inline(:date='event.createdAt')
        mid-dot(v-if="event.childCount")
          span(v-if="event.childCount" v-t="{path: 'thread_preview.replies_count', args: {count: event.childCount}}")

  .thread-item(v-if="!collapsed" :class="{'thread-item--unread': isUnread, 'thread-item--focused': isFocused}" v-observe-visibility="{callback: viewed}")
    v-layout.lmo-action-dock-wrapper(:id="'sequence-' + event.sequenceId" :class="{'thread-item--indent': indent}")
      .lmo-disabled-form(v-show='isDisabled')
      .thread-item__avatar.mr-3.mt-2
        div(@mouseover="hover = true" @mouseleave="hover = false")
          v-btn(v-if="hover" :width="iconSize" :height="iconSize"  icon @click="collapsed = true")
            v-icon mdi-arrow-collapse-vertical
          user-avatar(v-if='!hover && !event.isForkable() && event.actor()' :user='event.actor()' :size='iconSize')
          v-checkbox.thread-item__is-forking(v-if="!hover && event.isForkable()" :disabled="!event.canFork()" @change="event.toggleFromFork()" v-model="event.isForking()")
      v-layout.thread-item__body(column)
        v-layout.my-1.align-center.wrap
          h3.thread-item__title.body-2(:id="'event-' + event.id")
            //- div
              | sid {{event.sequenceId}}
              | pos {{event.position}}
              //- | pid: {{event.model().parentId}}
              | depth: {{event.depth}}
            slot(name="headline")
              span(v-html='headline')
            //- mid-dot
            //- v-spacer
          v-spacer
          .thread-item__link.grey--text.body-2.action-menu
            router-link.grey--text(:to='link')
              time-ago(:date='event.createdAt')
            space
          slot(name="actions")
        slot
  template(v-if='!collapsed && event.isSurface()')
    event-children(:parent-event='event')
</template>

<style lang="css">
.thread-item__title strong {
  font-weight: normal;
}

.thread-item {
  padding: 8px 16px 16px 16px;
}

.thread-item .v-card__actions {
    padding-left: 0;
    padding-right: 0;
}

.thread-item--focused {
  background-color: var(--v-accent-lighten5);
}

.thread-item--unread {
  padding-left: $cardPaddingSize - 2px;
  border-left: 2px solid var(--v-accent-base);
}
.thread-item--unread .thread-item--indent {
    padding-left: $cardPaddingSize + 40px;
    /* // padding-left: 56px; // (42 (indent) - 2 (unread border) + 16 (card padding)) */
}

.thread-item--indent {
  padding-left: 64px;
}

.thread-item__body {
  width: 100%;
  min-width: 0;
}

@media (max-width: $tiny-max-px){
  .thread-item__directive {
    margin-left: -42px;
  }
}

.thread-item__footer {
  /* // @include fontSmall(); */
  color: $grey-on-white;
  clear: both;
}

.thread-item__actions {
  margin-right: 3px;
  display: inline-block;
}

.thread-item__link:hover  {
  text-decoration: underline;
}

.thread-item__link abbr {
  font-weight: normal;
  color: $grey-on-white;
}

.thread-item__action {
  /* // @include lmoBtnLink; */
  color: $link-color;
}

.thread-item__action--view-edits {
  /* // @include lmoBtnLink; */
  color: $grey-on-white;
}

.thread-item__timestamp {
  display: inline-block;
}
</style>

<script lang="coffee">
import Session        from '@/shared/services/session'
import Records        from '@/shared/services/records'
import EventBus       from '@/shared/services/event_bus'
import AbilityService from '@/shared/services/ability_service'
import LmoUrlService  from '@/shared/services/lmo_url_service'

import { submitForm } from '@/shared/helpers/form'
import { eventHeadline, eventTitle, eventPollType } from '@/shared/helpers/helptext'
import { includes, camelCase } from 'lodash'

import selection from 'vue-text-selection'

export default
  props:
    event: Object
    eventWindow: Object

  watch:
    '$route.params.sequence_id': 'updateIsFocused'

  data: ->
    isDisabled: false
    isFocused: @updateIsFocused()
    collapsed: false
    hover: false

  methods:
    updateIsFocused: ->
      @isFocused = parseInt(@$route.params.sequence_id) == @event.sequenceId

    viewed: (viewed) ->
      @event.markAsRead() if viewed

    hasComponent: ->
      includes(threadItemComponents, camelCase(@event.kind))

    debug: ->
      window.Loomio.debug

    camelCase: camelCase

  computed:
    iconSize: -> if @isNested then 32 else 40

    isNested: -> @event.isNested()

    indent: ->
      @$vuetify.breakpoint.smAndUp && @event.isNested() # && @eventWindow.useNesting

    isUnread: ->
      (Session.user().id != @event.actorId) && false # @eventWindow.isUnread(@event)

    headline: ->
      @$t eventHeadline(@event, true ), # useNesting
        author:   @event.actorName() || @$t('common.anonymous')
        username: @event.actorUsername()
        key:      @event.model().key
        title:    eventTitle(@event)
        polltype: @$t(eventPollType(@event)).toLowerCase()

    link: ->
      LmoUrlService.event @event

</script>

<template lang="pug">
div
  .thread-item.thread-item--collapsed(v-if="collapsed")
    v-layout(:id="'sequence-' + event.sequenceId" :class="{'thread-item--indent': indent}")
      .thread-item__avatar.mr-3.mt-2.d-flex(@mouseover="hover = true" @mouseleave="hover = false")
        v-btn(v-if="hover" icon :width="iconSize" :height="iconSize"  @click="collapsed = false")
          v-icon mdi-arrow-expand-vertical
        user-avatar(v-if='!hover && !event.isForkable() && event.actor()' :user='event.actor()' :size='iconSize')
      h3.thread-item__title.body-2.my-1.d-flex.align-center.wrap(:id="'event-' + event.id")
        //- div
          | sid {{event.sequenceId}}
          | pos {{event.position}}
          //- | pid: {{event.model().parentId}}
          | depth: {{event.depth}}
        slot(name="headline")
          span(v-html='headline')
        mid-dot
        router-link.thread-item__link(:to='link')
          time-ago.timeago--inline(:date='event.createdAt')
        mid-dot(v-if="event.childCount")
          span(v-if="event.childCount" v-t="{path: 'thread_preview.replies_count', args: {count: event.childCount}}")

  .thread-item(v-if="!collapsed" :class="{'thread-item--unread': isUnread, 'thread-item--focused': isFocused}" v-observe-visibility="{callback: viewed}")
    v-layout.lmo-action-dock-wrapper(:id="'sequence-' + event.sequenceId" :class="{'thread-item--indent': indent}")
      .lmo-disabled-form(v-show='isDisabled')
      .thread-item__avatar.mr-3.mt-2
        div(@mouseover="hover = true" @mouseleave="hover = false")
          v-btn(v-if="hover" :width="iconSize" :height="iconSize"  icon @click="collapsed = true")
            v-icon mdi-arrow-collapse-vertical
          user-avatar(v-if='!hover && !event.isForkable() && event.actor()' :user='event.actor()' :size='iconSize')
          v-checkbox.thread-item__is-forking(v-if="!hover && event.isForkable()" :disabled="!event.canFork()" @change="event.toggleFromFork()" v-model="event.isForking()")
      v-layout.thread-item__body(column)
        v-layout.my-1.align-center.wrap
          h3.thread-item__title.body-2(:id="'event-' + event.id")
            //- div
              | sid {{event.sequenceId}}
              | pos {{event.position}}
              //- | pid: {{event.model().parentId}}
              | depth: {{event.depth}}
            slot(name="headline")
              span(v-html='headline')
            //- mid-dot
            //- v-spacer
          v-spacer
          .thread-item__link.grey--text.body-2.action-menu
            router-link.grey--text(:to='link')
              time-ago(:date='event.createdAt')
            space
          slot(name="actions")
        slot
  template(v-if='!collapsed && event.isSurface()')
    event-children(:parent-event='event')
</template>

<style lang="css">
.thread-item__title strong {
  font-weight: normal;
}

.thread-item {
  padding: 8px 16px 16px 16px;
}

.thread-item .v-card__actions {
    padding-left: 0;
    padding-right: 0;
}

.thread-item--focused {
  background-color: var(--v-accent-lighten5);
}

.thread-item--unread {
  padding-left: $cardPaddingSize - 2px;
  border-left: 2px solid var(--v-accent-base);
}
.thread-item--unread .thread-item--indent {
    padding-left: $cardPaddingSize + 40px;
    /* // padding-left: 56px; // (42 (indent) - 2 (unread border) + 16 (card padding)) */
}

.thread-item--indent {
  padding-left: 64px;
}

.thread-item__body {
  width: 100%;
  min-width: 0;
}

@media (max-width: $tiny-max-px){
  .thread-item__directive {
    margin-left: -42px;
  }
}

.thread-item__footer {
  /* // @include fontSmall(); */
  color: $grey-on-white;
  clear: both;
}

.thread-item__actions {
  margin-right: 3px;
  display: inline-block;
}

.thread-item__link:hover  {
  text-decoration: underline;
}

.thread-item__link abbr {
  font-weight: normal;
  color: $grey-on-white;
}

.thread-item__action {
  /* // @include lmoBtnLink; */
  color: $link-color;
}

.thread-item__action--view-edits {
  /* // @include lmoBtnLink; */
  color: $grey-on-white;
}

.thread-item__timestamp {
  display: inline-block;
}
</style>


Update Subscribe.vue

Update Subscribe.vue

<template>

      {{ this.button.order }}

Our space offers 🎁

🧠 Coworking

Located in downtown Bratislava. We accept Bitcoin, Ethereum & Litecoin!

🤝 Meeting room and a call room (todo internal link)

👀 Your view (todo addlink)

⚡ We have fast internet (~250/20), shared monitors and standing desks

❤ Special offers for minorities

🎶 Chromecast for projector and music

💪 Pull-up bar

🔧 Makerspace

🥤 Drinks

🍵 Tea (Matcha as well)

              🍼 Bottled and cooled drinks such as

                {{item.name}}

              ☕ Specialty Coffee (black.xyz) and also coffee machine and frenchpress

📅 Events

WIP waiting for omaksi

        <input v-model="subEmail" class="input is-medium is-white" type="email" placeholder="Enter your email">

      {{ this.button.subscribe }}
      {{ this.button.subscribe }}
      {{ this.button.login }}
      {{ this.button.login }}

🧠 Coworking

  <Offer />

📸 Photos

🐚 About Progressbar

Our backlog story

                We started as first Slovak hackerspace back in June 2010, this is our 4th place as we went through expansion and held more and biggers meetups.

                In September 2017 we considered to keep up our 💸 cashflow & our existence and started thinking about daily utilization. We came up with idea to open coworking in our hackerspace Progressbar in Bratislava.

                If you think we are doing great thing

                    you can support us at donate.progressbar.sk

                     signup for our coworking in Bratislava

                    <router-link to="/sponsors">be our proud sponsor</router-link>

                👋🏼 You are welcome 🤗

Our members are ~ 😎

                Blockchain startups

                Decentralization technology specialists

                Cryptocurrency techies & fanatics

Teachers, educators

                Backend & Frontend developers

                Machine learning developers

                Hardware tinkers

                Opensource developers

                Cyberpunks & Cypherpunks

DIYBio supporters

  <cookie-law transitionName="fade"
   position="bottom"
   theme="dark-lime"
   buttonText="I understand"></cookie-law>

</template>

<template>

      {{ this.button.order }}

Our space offers 🎁

🧠 Coworking

Located in downtown Bratislava. We accept Bitcoin, Ethereum & Litecoin!

🤝 Meeting room and a call room (todo internal link)

👀 Your view (todo addlink)

⚡ We have fast internet (~250/20), shared monitors and standing desks

❤ Special offers for minorities

🎶 Chromecast for projector and music

💪 Pull-up bar

🔧 Makerspace

🥤 Drinks

🍵 Tea (Matcha as well)

              🍼 Bottled and cooled drinks such as

                {{item.name}}

              ☕ Specialty Coffee (black.xyz) and also coffee machine and frenchpress

📅 Events

WIP waiting for omaksi

        <input v-model="subEmail" class="input is-medium is-white" type="email" placeholder="Enter your email">

      {{ this.button.subscribe }}
      {{ this.button.subscribe }}
      {{ this.button.login }}
      {{ this.button.login }}

🧠 Coworking

  <Offer />

📸 Photos

🐚 About Progressbar

Our backlog story

                We started as first Slovak hackerspace back in June 2010, this is our 4th place as we went through expansion and held more and biggers meetups.

                In September 2017 we considered to keep up our 💸 cashflow & our existence and started thinking about daily utilization. We came up with idea to open coworking in our hackerspace Progressbar in Bratislava.

                If you think we are doing great thing

                    you can support us at donate.progressbar.sk

                     signup for our coworking in Bratislava

                    <router-link to="/sponsors">be our proud sponsor</router-link>

                👋🏼 You are welcome 🤗

Our coworkers are ~ 😎

                Blockchain startups

                Decentralization technology specialists

                Cryptocurrency techies & fanatics

Teachers, educators

                Backend & Frontend developers

                Machine learning developers

                Hardware tinkers

                Opensource developers

                Cyberpunks & Cypherpunks

DIYBio supporters

  <cookie-law transitionName="fade"
   position="bottom"
   theme="dark-lime"
   buttonText="I understand"></cookie-law>

</template>


fix: update wallet header on ledger disconnect (#1361)

<template>
  <div
    :class="justifyClass"
    class="WalletHeading flex px-10 py-8 w-full bg-theme-heading-background rounded-tl-lg h-40 overflow-hidden"
  >
    <WalletHeadingInfo
      v-if="!secondaryButtonsVisible"
      ref="heading"
    />
    <WalletHeadingActions />
  </div>
</template>

<script>
import WalletHeadingInfo from './WalletHeadingInfo'
import WalletHeadingActions from './WalletHeadingActions'
import { mapGetters } from 'vuex'

export default {
  name: 'WalletHeading',

  components: {
    WalletHeadingInfo,
    WalletHeadingActions
  },

  data: () => ({
    activeWalletAddress: null
  }),

  computed: {
    ...mapGetters('wallet', ['secondaryButtonsVisible']),

    currentWallet () {
      return this.wallet_fromRoute
    },

    justifyClass () {
      return this.secondaryButtonsVisible ? 'justify-end' : 'justify-between'
    }
  },

  watch: {
    currentWallet () {
      if (this.activeWalletAddress !== this.currentWallet.address) {
        this.resetHeading()
      }
    }
  },

  async created () {
    this.$eventBus.on('ledger:disconnected', this.refreshWallet)
  },

  beforeDestroy () {
    this.$eventBus.off('ledger:disconnected', this.refreshWallet)
  },

  mounted () {
    this.resetHeading()
  },

  methods: {
    resetHeading () {
      this.activeWalletAddress = this.currentWallet.address
      this.$store.dispatch('wallet/setSecondaryButtonsVisible', false)
      this.refreshWallet()
    },

    refreshWallet () {
      this.$nextTick(() => {
        this.$refs.heading.refreshWallet()
      })
    }
  }
}
</script>

<style>
.WalletHeading {
  background-image: -webkit-linear-gradient(30deg, var(--theme-transaction-detail-gradient1) 130px, var(--theme-transaction-detail-gradient2) 130px);
}
</style>

<template>
  <div
    :class="justifyClass"
    class="WalletHeading flex px-10 py-8 w-full bg-theme-heading-background rounded-tl-lg h-40 overflow-hidden"
  >
    <WalletHeadingInfo
      v-if="!secondaryButtonsVisible"
      ref="heading"
    />
    <WalletHeadingActions />
  </div>
</template>

<script>
import WalletHeadingInfo from './WalletHeadingInfo'
import WalletHeadingActions from './WalletHeadingActions'
import { mapGetters } from 'vuex'

export default {
  name: 'WalletHeading',

  components: {
    WalletHeadingInfo,
    WalletHeadingActions
  },

  data: () => ({
    activeWalletId: null
  }),

  computed: {
    ...mapGetters('wallet', ['secondaryButtonsVisible']),

    currentWallet () {
      return this.wallet_fromRoute
    },

    justifyClass () {
      return this.secondaryButtonsVisible ? 'justify-end' : 'justify-between'
    }
  },

  watch: {
    currentWallet () {
      if (this.activeWalletId !== this.currentWallet.id) {
        this.resetHeading()
      }
    }
  },

  mounted () {
    this.resetHeading()
  },

  methods: {
    resetHeading () {
      this.activeWalletId = this.currentWallet.id
      this.$store.dispatch('wallet/setSecondaryButtonsVisible', false)
      this.$nextTick(() => {
        this.$refs.heading.refreshWallet()
      })
    }
  }
}
</script>

<style>
.WalletHeading {
  background-image: -webkit-linear-gradient(30deg, var(--theme-transaction-detail-gradient1) 130px, var(--theme-transaction-detail-gradient2) 130px);
}
</style>


Search: Add event:on-cancel

Search: Add event:on-cancel

#327

<template>

      <form class="weui_search_outer" @submit.prevent="$emit('on-submit', value)">

          <input type="text" class="weui_search_input" id="search_input" placeholder="{{placeholder}}" autocomplete="off" required v-model="value" v-el:input/>

          {{placeholder}}

      </form>
      {{cancelText}}

{{item.title}}

</template>


Search: Set type=search. close #484

Search: Set type=search. close #484

<template>

      <form class="weui_search_outer" @submit.prevent="$emit('on-submit', value)">

          <input type="search" class="weui_search_input" id="search_input" placeholder="{{placeholder}}" autocomplete="off" required v-model="value" v-el:input/>

          {{placeholder}}

      </form>
      {{cancelText}}

{{item.title}}

</template>