Don't allow t3 imported competencies to have no name CA-631

<template>

                                        Import a concept scheme

                                    Import a framework

                                    Upload documents to transform into CaSS Concept Schemes.

                                    Upload documents to transform into CaSS Competency Frameworks.

                                        CaSS has detected a framework!

                                    Please review the competency framework and file details gathered below. To continue with the input of this competency framework, press Accept Details & Review. To cancel and review or change your input file, press cancel.

                                        Import success, {{ frameworkSize }} competencies ready to edit.

                                        Import success, concepts ready to edit.

                                        Your import is complete!

                        <ImportTabs
                            v-if="!importFramework ||(importFramework && importType==='text')"
                            :caseDocs="caseDocs"
                            :csvRelationFile="csvRelationFile"
                            :csvRelationColumns="csvRelationColumns"
                            :importCsvColumnSource="importCsvColumnSource"
                            :importCsvColumnRelationType="importCsvColumnRelationType"
                            :importCsvColumnTarget="importCsvColumnTarget"
                            :csvColumns="csvColumns"
                            @analyzeCsvRelation="analyzeCsvRelation($event)"
                            @importCase="handleImportFromTabs($event)" />

                        <ImportDetails
                            :detailsDetected="detailsDetected"
                            v-if="importTransition === 'detail'" />

                            <Component
                                :is="dynamicThing"
                                @editNodeEvent="onEditNode"
                                @doneEditingNodeEvent="onDoneEditingNode"
                                :class="{'is-hidden': !hierarchyIsdoneLoading}"
                                :obj="changedObj ? changedObj : importFramework"
                                :repo="repo"
                                class="framework-title"
                                :profile="conceptMode ? null : t3FrameworkProfile" />

                            <Hierarchy
                                :class="{'is-hidden': !hierarchyIsdoneLoading}"
                                view="importPreview"
                                v-if="importFramework && !conceptMode"
                                @doneLoadingNodes="handleDoneLoading"
                                @searchThings="handleSearch($event)"
                                @editMultipleEvent="onEditMultiple"
                                :container="importFramework"
                                containerType="Framework"
                                containerNodeProperty="competency"
                                containerEdgeProperty="relation"
                                nodeType="EcCompetency"
                                :profile="t3CompetencyProfile"
                                :viewOnly="false"
                                :isDraggable="true"
                                edgeType="EcAlignment"
                                edgeRelationProperty="relationType"
                                edgeRelationLiteral="narrows"
                                edgeSourceProperty="source"
                                edgeTargetProperty="target"
                                :repo="repo"
                                @selectedArray="selectedArrayEvent"
                                :newFramework="true"
                                @deleteObject="deleteObject" />
                            <ConceptHierarchy
                                :class="{'is-hidden': !hierarchyIsdoneLoading}"
                                view="import"
                                v-if="importFramework && conceptMode"
                                @doneLoadingNodes="handleDoneLoading"
                                @searchThings="handleSearch($event)"
                                @editMultipleEvent="onEditMultiple"
                                :container="importFramework"
                                containerType="ConceptScheme"
                                :viewOnly="false"
                                :isDraggable="true"
                                :repo="repo"
                                @selectedArray="selectedArrayEvent"
                                :newFramework="true"
                                @deleteObject="deleteObject" />

                            <Component
                                :is="dynamicThing"
                                :editingNode="editingNode"
                                @doneEditingNodeEvent="onDoneEditingNode"
                                :obj="changedObj ? changedObj : importFramework"
                                :parentNotEditable="true"
                                class="framework-title"
                                :profile="conceptMode ? null : t3FrameworkProfile" />
                            <Hierarchy
                                v-if="importFramework && !conceptMode"
                                view="importLight"
                                :container="importFramework"
                                containerType="Framework"
                                containerNodeProperty="competency"
                                containerEdgeProperty="relation"
                                nodeType="EcCompetency"
                                :profile="t3CompetencyProfile"
                                :editable="false"
                                :viewOnly="true"
                                edgeType="EcAlignment"
                                edgeRelationProperty="relationType"
                                edgeRelationLiteral="narrows"
                                edgeSourceProperty="source"
                                edgeTargetProperty="target"
                                :repo="repo"
                                :newFramework="true"
                                @deleteObject="deleteObject" />
                            <ConceptHierarchy
                                :class="{'is-hidden': !hierarchyIsdoneLoading}"
                                view="import"
                                v-if="importFramework && conceptMode"
                                :container="importFramework"
                                containerType="ConceptScheme"
                                :viewOnly="true"
                                :repo="repo"
                                @selectedArray="selectedArrayEvent"
                                :newFramework="true"
                                @deleteObject="deleteObject" />

</template>


TLにNote追加時にdeepcopyする (#6275)

TLにNote追加時にdeepcopyする (#6275)

<template>
<x-notes ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/>
</template>

<script lang="ts">
import Vue from 'vue';
import XNotes from './notes.vue';

export default Vue.extend({
	components: {
		XNotes
	},

	props: {
		src: {
			type: String,
			required: true
		},
		list: {
			required: false
		},
		antenna: {
			required: false
		},
		sound: {
			type: Boolean,
			required: false,
			default: false,
		}
	},

	data() {
		return {
			connection: null,
			connection2: null,
			pagination: null,
			baseQuery: {
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
			},
			query: {},
		};
	},

	created() {
		this.$once('hook:beforeDestroy', () => {
			this.connection.dispose();
			if (this.connection2) this.connection2.dispose();
		});

		const prepend = note => {
			const _note = JSON.parse(JSON.stringify(note));	// deepcopy
			(this.$refs.tl as any).prepend(_note);

			if (this.sound) {
				this.$root.sound(note.userId === this.$store.state.i.id ? 'noteMy' : 'note');
			}
		};

		const onUserAdded = () => {
			(this.$refs.tl as any).reload();
		};

		const onUserRemoved = () => {
			(this.$refs.tl as any).reload();
		};

		const onChangeFollowing = () => {
			if (!this.$refs.tl.backed) {
				this.$refs.tl.reload();
			}
		};

		let endpoint;

		if (this.src == 'antenna') {
			endpoint = 'antennas/notes';
			this.query = {
				antennaId: this.antenna.id
			};
			this.connection = this.$root.stream.connectToChannel('antenna', {
				antennaId: this.antenna.id
			});
			this.connection.on('note', prepend);
		} else if (this.src == 'home') {
			endpoint = 'notes/timeline';
			this.connection = this.$root.stream.useSharedConnection('homeTimeline');
			this.connection.on('note', prepend);

			this.connection2 = this.$root.stream.useSharedConnection('main');
			this.connection2.on('follow', onChangeFollowing);
			this.connection2.on('unfollow', onChangeFollowing);
		} else if (this.src == 'local') {
			endpoint = 'notes/local-timeline';
			this.connection = this.$root.stream.useSharedConnection('localTimeline');
			this.connection.on('note', prepend);
		} else if (this.src == 'social') {
			endpoint = 'notes/hybrid-timeline';
			this.connection = this.$root.stream.useSharedConnection('hybridTimeline');
			this.connection.on('note', prepend);
		} else if (this.src == 'global') {
			endpoint = 'notes/global-timeline';
			this.connection = this.$root.stream.useSharedConnection('globalTimeline');
			this.connection.on('note', prepend);
		} else if (this.src == 'list') {
			endpoint = 'notes/user-list-timeline';
			this.query = {
				listId: this.list.id
			};
			this.connection = this.$root.stream.connectToChannel('userList', {
				listId: this.list.id
			});
			this.connection.on('note', prepend);
			this.connection.on('userAdded', onUserAdded);
			this.connection.on('userRemoved', onUserRemoved);
		}

		this.pagination = {
			endpoint: endpoint,
			limit: 10,
			params: init => ({
				untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
				...this.baseQuery, ...this.query
			})
		};
	},

	methods: {
		focus() {
			this.$refs.tl.focus();
		}
	}
});
</script>

<template>
<x-notes ref="tl" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/>
</template>

<script lang="ts">
import Vue from 'vue';
import XNotes from './notes.vue';

export default Vue.extend({
	components: {
		XNotes
	},

	props: {
		src: {
			type: String,
			required: true
		},
		list: {
			required: false
		},
		antenna: {
			required: false
		},
		sound: {
			type: Boolean,
			required: false,
			default: false,
		}
	},

	data() {
		return {
			connection: null,
			connection2: null,
			pagination: null,
			baseQuery: {
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
			},
			query: {},
		};
	},

	created() {
		this.$once('hook:beforeDestroy', () => {
			this.connection.dispose();
			if (this.connection2) this.connection2.dispose();
		});

		const prepend = note => {
			(this.$refs.tl as any).prepend(note);

			if (this.sound) {
				this.$root.sound(note.userId === this.$store.state.i.id ? 'noteMy' : 'note');
			}
		};

		const onUserAdded = () => {
			(this.$refs.tl as any).reload();
		};

		const onUserRemoved = () => {
			(this.$refs.tl as any).reload();
		};

		const onChangeFollowing = () => {
			if (!this.$refs.tl.backed) {
				this.$refs.tl.reload();
			}
		};

		let endpoint;

		if (this.src == 'antenna') {
			endpoint = 'antennas/notes';
			this.query = {
				antennaId: this.antenna.id
			};
			this.connection = this.$root.stream.connectToChannel('antenna', {
				antennaId: this.antenna.id
			});
			this.connection.on('note', prepend);
		} else if (this.src == 'home') {
			endpoint = 'notes/timeline';
			this.connection = this.$root.stream.useSharedConnection('homeTimeline');
			this.connection.on('note', prepend);

			this.connection2 = this.$root.stream.useSharedConnection('main');
			this.connection2.on('follow', onChangeFollowing);
			this.connection2.on('unfollow', onChangeFollowing);
		} else if (this.src == 'local') {
			endpoint = 'notes/local-timeline';
			this.connection = this.$root.stream.useSharedConnection('localTimeline');
			this.connection.on('note', prepend);
		} else if (this.src == 'social') {
			endpoint = 'notes/hybrid-timeline';
			this.connection = this.$root.stream.useSharedConnection('hybridTimeline');
			this.connection.on('note', prepend);
		} else if (this.src == 'global') {
			endpoint = 'notes/global-timeline';
			this.connection = this.$root.stream.useSharedConnection('globalTimeline');
			this.connection.on('note', prepend);
		} else if (this.src == 'list') {
			endpoint = 'notes/user-list-timeline';
			this.query = {
				listId: this.list.id
			};
			this.connection = this.$root.stream.connectToChannel('userList', {
				listId: this.list.id
			});
			this.connection.on('note', prepend);
			this.connection.on('userAdded', onUserAdded);
			this.connection.on('userRemoved', onUserRemoved);
		}

		this.pagination = {
			endpoint: endpoint,
			limit: 10,
			params: init => ({
				untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined),
				...this.baseQuery, ...this.query
			})
		};
	},

	methods: {
		focus() {
			this.$refs.tl.focus();
		}
	}
});
</script>


always provide valid zoom level to Leaflet

<template>
  <l-map
    class="k-map"
    :bounds="bounds"
    :center="center"
    :zoom="zoom"
    @click="$emit('mapClick', arguments[0].latlng)"
    @moveend="$emit('mapMoveEnd', arguments[0].target)"
    @update:zoom="updateZoom"
  >
    <l-tile-layer
      :url="url"
      :attribution="attribution"
    />
    <ExtendedMarker
      v-for="marker in leafletMarkers"
      :key="marker.id"
      v-bind="marker"
      @dragend="$emit('markerMoved', $event.target._latlng, marker)"
      :opacity="opacityFor(marker)"
    >
      <l-popup
        v-if="marker.popupcontent"
        :content="marker.popupcontent"
      />
    </ExtendedMarker>
  </l-map>
</template>

<template>
  <l-map
    class="k-map"
    :bounds="bounds"
    :center="center"
    :zoom="zoom"
    @click="$emit('mapClick', arguments[0].latlng)"
    @moveend="$emit('mapMoveEnd', arguments[0].target)"
  >
    <l-tile-layer
      :url="url"
      :attribution="attribution"
    />
    <ExtendedMarker
      v-for="marker in leafletMarkers"
      :key="marker.id"
      v-bind="marker"
      @dragend="$emit('markerMoved', $event.target._latlng, marker)"
      :opacity="opacityFor(marker)"
    >
      <l-popup
        v-if="marker.popupcontent"
        :content="marker.popupcontent"
      />
    </ExtendedMarker>
  </l-map>
</template>


fix(MdChips):  fix issues (#1159)

fix(MdChips):  fix issues (#1159)

* fix(MdChips): inputValue default value

inputValue should be `''` at default.

* fix(MdChips): remove unnecessary plus symbol

* fix(MdChips): Preventing empty chip

* refactor(MdChips): Changing modelRespectLimit from methods to computed

* fix(MdChips): Removing input after reaching limit

<template>
  <md-field class="md-chips" :class="[$mdActiveTheme, chipsClasses]">
    <slot v-if="!mdStatic" />

    <md-chip
      v-for="(chip, key) in value"
      :key="key"
      :md-deletable="!mdStatic"
      :md-clickable="!mdStatic"
      @keydown.enter="$emit('md-click', chip, key)"
      @click.native="$emit('md-click', chip, key)"
      @md-delete.stop="removeChip(chip)">
      <slot name="md-chip" :chip="chip" v-if="$scopedSlots['md-chip']">{{ chip }}</slot>
      <template v-else>{{ chip }}</template>
    </md-chip>

    <md-input
      ref="input"
      v-model.trim="inputValue"
      v-if="!mdStatic && modelRespectLimit"
      :type="mdInputType"
      :id="id"
      :placeholder="mdPlaceholder"
      @keydown.enter="insertChip"
      @keydown.8="handleBackRemove">
    </md-input>
  </md-field>
</template>

<script>
  import MdComponent from 'core/MdComponent'
  import MdField from 'components/MdField/MdField'
  import MdInput from 'components/MdField/MdInput/MdInput'
  import MdUuid from 'core/utils/MdUuid'
  import MdPropValidator from 'core/utils/MdPropValidator'

  export default new MdComponent({
    name: 'MdChips',
    components: {
      MdField,
      MdInput
    },
    props: {
      value: Array,
      id: {
        type: [String, Number],
        default: () => 'md-chips-' + MdUuid()
      },
      mdInputType: {
        type: [String, Number],
        ...MdPropValidator('md-input-type', ['email', 'number', 'password', 'search', 'tel', 'text', 'url'])
      },
      mdPlaceholder: [String, Number],
      mdStatic: Boolean,
      mdLimit: Number
    },
    data: () => ({
      inputValue: ''
    }),
    computed: {
      chipsClasses () {
        return {
          'md-has-value': this.value && this.value.length
        }
      },

      modelRespectLimit () {
        return !this.mdLimit || this.value.length < this.mdLimit
      }
    },
    methods: {
      insertChip ({ target }) {
        if (
          !this.inputValue ||
          this.value.includes(this.inputValue) ||
          !this.modelRespectLimit
        ) {
          return
        }
        this.value.push(this.inputValue)
        this.$emit('input', this.value)
        this.$emit('md-insert', this.inputValue)
        this.inputValue = ''
      },
      removeChip (chip) {
        const index = this.value.indexOf(chip)

        this.value.splice(index, 1)
        this.$emit('input', this.value)
        this.$emit('md-delete', chip, index)
        this.$nextTick(() => this.$refs.input.$el.focus())
      },
      handleBackRemove () {
        if (!this.inputValue) {
          this.removeChip(this.value[this.value.length - 1])
        }
      }
    }
  })
</script>

<style lang="scss">
  @import "~components/MdAnimation/variables";

  .md-chips.md-field {
    padding-top: 12px;
    flex-wrap: wrap;
    transition: .3s $md-transition-default-timing;

    &.md-has-value {
      label {
        top: -6px;
      }
    }

    .md-chip {
      margin-bottom: 4px;

      &:last-of-type {
        margin-right: 8px;
      }
    }

    .md-input {
      min-width: 128px;
    }
  }
</style>

<template>
  <md-field class="md-chips" :class="[$mdActiveTheme, chipsClasses]">
    <slot v-if="!mdStatic" />

    <md-chip
      v-for="(chip, key) in value"
      :key="key"
      :md-deletable="!mdStatic"
      :md-clickable="!mdStatic"
      @keydown.enter="$emit('md-click', chip, key)"
      @click.native="$emit('md-click', chip, key)"
      @md-delete.stop="removeChip(chip)">
      <slot name="md-chip" :chip="chip" v-if="$scopedSlots['md-chip']">{{ chip }}</slot>
      <template v-else>{{ chip }}</template>
    </md-chip>

    <md-input
      ref="input"
      v-model.trim="inputValue"
      v-if="!mdStatic"
      :type="mdInputType"
      :id="id"
      :placeholder="mdPlaceholder"
      @keydown.enter="insertChip"
      @keydown.8="handleBackRemove">
    </md-input>
  </md-field>
</template>

<script>
  import MdComponent from 'core/MdComponent'
  import MdField from 'components/MdField/MdField'
  import MdInput from 'components/MdField/MdInput/MdInput'
  import MdUuid from 'core/utils/MdUuid'
  import MdPropValidator from 'core/utils/MdPropValidator'

  export default new MdComponent({
    name: 'MdChips',
    components: {
      MdField,
      MdInput
    },
    props: {
      value: Array,
      id: {
        type: [String, Number],
        default: () => 'md-chips-' + MdUuid()
      },
      mdInputType: {
        type: [String, Number],
        ...MdPropValidator('md-input-type', ['email', 'number', 'password', 'search', 'tel', 'text', 'url'])
      },
      mdPlaceholder: [String, Number],
      mdStatic: Boolean,
      mdLimit: Number
    },
    data: () => ({
      inputValue: null
    }),
    computed: {
      chipsClasses () {
        return {
          'md-has-value': this.value && this.value.length
        }
      }
    },
    methods: {
      modelRespectLimit () {
        return !this.mdLimit || this.value.length < +this.mdLimit
      },
      insertChip ({ target }) {
        if (!this.value.includes(this.inputValue) && this.modelRespectLimit()) {
          this.value.push(this.inputValue)
          this.$emit('input', this.value)
          this.$emit('md-insert', this.inputValue)
          this.inputValue = ''
        }
      },
      removeChip (chip) {
        const index = this.value.indexOf(chip)

        this.value.splice(index, 1)
        this.$emit('input', this.value)
        this.$emit('md-delete', chip, index)
        this.$refs.input.$el.focus()
      },
      handleBackRemove () {
        if (!this.inputValue) {
          this.removeChip(this.value[this.value.length - 1])
        }
      }
    }
  })
</script>

<style lang="scss">
  @import "~components/MdAnimation/variables";

  .md-chips.md-field {
    padding-top: 12px;
    flex-wrap: wrap;
    transition: .3s $md-transition-default-timing;

    &.md-has-value {
      label {
        top: -6px;
      }
    }

    .md-chip {
      margin-bottom: 4px;

      &:last-of-type {
        margin-right: 8px;
      }
    }

    .md-input {
      min-width: 128px;
    }
  }
</style>


lint fix

lint fix

<template>
    <div class="flexbox" :class="className" :style="styles">
        <slot></slot>
    </div>
</template>

<script>
export default {
    props: {
        gutter: {
            type: Number,
            default: 8,
        },
        direction: {
            type: String,
            default: 'horizontal',
        },
        justify: String,
        align: String,
        wrap: String,
        class: String,
    },
    computed: {
        styles() {
            const styles = {
                'justify-content': this.justify,
                '-webkit-justify-content': this.justify,
                'align-items': this.align,
                '-webkit-align-items': this.align,
                'flex-wrap': this.wrap,
                '-webkit-flex-wrap': this.wrap,
                'flex-direction': this.direction,
                '-webkit-flex-direction': this.direction,
            };

            return styles;
        },
        className() {
            return this.class;
        },
    },
};
</script>

<style>
    .flexbox {
        width: 100%;
        text-align: left;
        display: flex;
        display: -webkit-flex;
        /*box-align: center;*/
        align-items: center;
    }

    .flexbox .flexbox-item {
        flex: 1;
        -webkit-flex: 1;
        min-width: 20px;
        width: 0%;
    }

    .flexbox flexbox-item:first-child {
        margin-left: 0 !important;
        margin-top: 0 !important;
    }
</style>

<template>
    <div class="flexbox" :class="className" :style="styles">
        <slot></slot>
    </div>
</template>

<script>
export default {
    props: {
        gutter: {
            type: Number,
            default: 8
        },
        direction: {
            type: String,
            default: 'horizontal'
        },
        justify: String,
        align: String,
        wrap: String,
        class: String,
    },
    computed: () {
        styles() {
            const styles = {
                'justify-content': this.justify,
                '-webkit-justify-content': this.justify,
                'align-items': this.align,
                '-webkit-align-items': this.align,
                'flex-wrap': this.wrap,
                '-webkit-flex-wrap': this.wrap,
                'flex-direction': this.direction,
                '-webkit-flex-direction': this.direction,
            };

            return styles;
        },
        className() {
            return this.class
        }
    }
}
</script>

<style>
    .flexbox {
        width: 100%;
        text-align: left;
        display: flex;
        display: -webkit-flex;
        /*box-align: center;*/
        align-items: center;
    }

    .flexbox .flexbox-item {
        flex: 1;
        -webkit-flex: 1;
        min-width: 20px;
        width: 0%;
    }

    .flexbox flexbox-item:first-child {
        margin-left: 0 !important;
        margin-top: 0 !important;
    }
</style>


工具列RWD：小於iPad用選單導航

工具列RWD：小於iPad用選單導航

<template>

      <el-row type="flex" justify="center" align="middle">

        <el-col :lg="8" :md="8" :sm="8" :xs="12">
          <el-input placeholder="篩選" v-model.lazy.trim="query" :disabled="$route.path === '/full_map'">
            <el-select v-model="scope" slot="prepend" placeholder="請選擇" size="large">
              <el-option label="Tag" value="tags"></el-option>
              <el-option label="標題" value="name"></el-option>
              <el-option label="簡介" value="desc"></el-option>
            </el-select>

          </el-input>
        </el-col>

        <el-col :lg="8" :md="8" :sm="8" :xs="8">

          <el-radio-group @change="goTo($event)" class="regular-toolbar" v-model="foo">
            <el-radio-button label="專案"></el-radio-button>
            <el-radio-button label="HackingMap"></el-radio-button>
            <el-radio-button label="場地"></el-radio-button>
          </el-radio-group>

          <el-select @change="goTo($event)" class="phone-toolbar" v-model="bar">
            <el-option key="projects" label="專案" value="專案"></el-option>
            <el-option key="map" label="HackingMap" value="HackingMap"></el-option>
            <el-option key="full_map" label="場地" value="場地"></el-option>
          </el-select>
        </el-col>

        <el-col :lg="8" :md="8" :sm="8" :xs="4">
          <el-radio-group v-model="sortKey" :disabled="$route.path === '/full_map'">
            <el-radio label="timestamp">最近更新</el-radio>
            <el-radio label="heartCount">最多<icon name="heart"></icon></el-radio>
            <el-radio label="starCount">最多<icon name="star"></icon></el-radio>
          </el-radio-group>
        </el-col>
      </el-row>

    <router-view v-bind:filteredPosts="filteredSortedPosts"></router-view>

    <template v-if="user !== null">
      <el-button icon="edit" id="editBtn" @click="showDialog = true" type="info" :plain="true"></el-button>
      <el-dialog
        :title="myPostTitle"
        :visible.sync="showDialog"
        :modal='true'
        :close-on-click-modal='false'>
        <myposts></myposts>
      </el-dialog>
    </template>
    <template v-else>
      <el-button icon="edit" id="editBtn" :disabled="true"></el-button>
    </template>

</template>

<template>

      <el-row type="flex" justify="center" align="middle">

        <el-col :span="8">
          <el-input placeholder="篩選" v-model.lazy.trim="query" :disabled="$route.path === '/full_map'">
            <el-select v-model="scope" slot="prepend" placeholder="請選擇" size="large">
              <el-option label="Tag" value="tags"></el-option>
              <el-option label="標題" value="name"></el-option>
              <el-option label="簡介" value="desc"></el-option>
            </el-select>

          </el-input>
        </el-col>

        <el-col :span="8">
          <el-button-group>
            <router-link to="/projects" tag="el-button">專案</router-link>
            <router-link to="/map" tag="el-button">HackingMap</router-link>
            <router-link to="/full_map" tag="el-button">場地</router-link>
          </el-button-group>
        </el-col>

        <el-col :span="8">
          <el-radio-group v-model="sortKey" :disabled="$route.path === '/full_map'" style="vertical-align:middle !important;">
            <el-radio label="timestamp">最近更新</el-radio>
            <el-radio label="heartCount">最多<icon name="heart"></icon></el-radio>
            <el-radio label="starCount">最多<icon name="star"></icon></el-radio>
          </el-radio-group>
        </el-col>
      </el-row>

    <router-view v-bind:filteredPosts="filteredSortedPosts"></router-view>

    <template v-if="user !== null">
      <el-button icon="edit" id="editBtn" @click="showDialog = true" type="info" :plain="true"></el-button>
      <el-dialog
        :title="myPostTitle"
        :visible.sync="showDialog"
        :modal='true'
        :close-on-click-modal='false'>
        <myposts></myposts>
      </el-dialog>
    </template>
    <template v-else>
      <el-button icon="edit" id="editBtn" :disabled="true"></el-button>
    </template>

</template>


Removed old parallax init

Removed old parallax init

<template lang="pug">
.parallax-container(v-bind:class="{ video:video }")
		.parallax(ref="parallax")
			img(v-if="type == 'image'" v-bind:src="imgLink" v-bind:alt="alt")
			video(v-if="type == 'video'" muted autoplay loop v-bind:poster="imgLink")
				source(v-bind:src="video" type="video/mp4")
</template>

<script>
import imageService from "@/helpers/imageService.js"

export default {
	name: "app-parallax",
	props: {
		type: { type: String, default: "image" },
		image: { type: String },
		video: { type: String, default: "" },
		alt: { type: String }
	},
	computed: {
		imgLink: function() {
			return imageService.get(this.image)
		}
	},
	mounted: function() {
		const elem = this.$refs.parallax
		const instance = M.Parallax.init(elem, {})
	}
}
</script>

<style lang="stylus" scoped>
.parallax-container
	height 30rem

	&.video
		@media MobileOnly
			height 250px
			margin-bottom -100px

		@media TabletOnly
			height 550px
			margin-bottom -250px

		@media DesktopOnly
			margin-bottom 0

		video
			width 100vw
</style>

<template lang="pug">
.parallax-container(v-bind:class="{ video:video }")
		.parallax
			img(v-if="type == 'image'" v-bind:src="imgLink" v-bind:alt="alt")
			video(v-if="type == 'video'" muted autoplay loop v-bind:poster="imgLink")
				source(v-bind:src="video" type="video/mp4")
</template>

<script>
import imageService from "@/helpers/imageService.js"

$(document).ready(() => {
	$(".parallax").parallax()
})

export default {
	name: "app-parallax",
	props: {
		type: { type: String, default: "image" },
		image: { type: String },
		video: { type: String, default: "" },
		alt: { type: String }
	},
	computed: {
		imgLink: function() {
			return imageService.get(this.image)
		}
	}
}
</script>

<style lang="stylus" scoped>
.parallax-container
	height 30rem

	&.video
		@media MobileOnly
			height 250px
			margin-bottom -100px

		@media TabletOnly
			height 550px
			margin-bottom -250px

		@media DesktopOnly
			margin-bottom 0

		video
			width 100vw
</style>


Fixed search results layout.

Fixed search results layout.

<template>
    <v-navigation-drawer
            fixed
            :clipped="clipped"
            v-model="drawer"
            app
    >
        <v-list>
            <search-input></search-input>

                <searchResults></searchResults>

            <v-list-tile>
                <v-list-tile-content>
                    <router-link :to="{ name: 'home' }">Introduction</router-link>
                </v-list-tile-content>
            </v-list-tile>

            <v-divider></v-divider>

                    <sidebar-item v-for="(articles, rootCategory) in $store.state.core.articles"
                                  :key="rootCategory"
                                  :category="rootCategory"
                                  :articles="articles"></sidebar-item>

        </v-list>
    </v-navigation-drawer>
</template>

<template>
    <v-navigation-drawer
            fixed
            :clipped="clipped"
            v-model="drawer"
            app
    >
        <v-list>
            <search-input></search-input>

            <v-list-tile>
                <v-list-tile-content>
                    <router-link :to="{ name: 'home' }">Introduction</router-link>
                </v-list-tile-content>
            </v-list-tile>

                <searchResults></searchResults>

            <v-divider></v-divider>

                    <sidebar-item v-for="(articles, rootCategory) in $store.state.core.articles"
                                  :key="rootCategory"
                                  :category="rootCategory"
                                  :articles="articles"></sidebar-item>

        </v-list>
    </v-navigation-drawer>
</template>


Fixed #122 and hide lines to hidden nodes

<template>

Map

Search: <input v-model="searchQuery">

				<v-map ref="leafletMap" :zoom="zoom" :center="center" style="height: 40em; margin-top: 1em">
					<v-group v-for="item in coverageLayers" :key="item.name">
						<v-image-overlay :url="item.url" :bounds="item.bounds" :opacity="0.8"></v-image-overlay>
					</v-group>
					<v-tilelayer :url="url" :attribution="attribution"></v-tilelayer>
					<v-marker v-for="item in markers" :key="item.name" :lat-lng="item.position" :icon="item.icon" v-on:l-click="showCoverage(item.name)">
						<v-popup :content="item.popup"></v-popup>
					</v-marker>
					<v-polyline v-for="item in lines" :key="item.name" :lat-lngs="item.position" :color="item.color"></v-polyline>
				</v-map>

Settings

					<input type="checkbox" v-model="settings.onlineOnly"> Show online transmitters only

					<input type="checkbox" v-model="settings.widerangeOnly"> Show Widerange-transmitter only

					<input type="checkbox" v-model="settings.timeslot.active"> Show transmitters by timeslots
						<input type="text" placeholder="timeslots" v-model="settings.timeslot.input" :disabled="!settings.timeslot.active">

					<input type="checkbox" v-model="settings.showNodes"> Show nodes

					<input type="checkbox" v-model="settings.showLines" :disabled="!settings.showNodes"> Show line from transmitter to node

Actions

Show all coverage-overlays

Remove all coverage-overlays

</template>


fix: 🐛 nonce issue where user is sending lower nonce than address

<template>

    <v-dialog v-model="dialog" persistent max-width="500">
      <v-sheet color="white" class="pa-5">

Transaction status

          <v-progress-linear indeterminate rounded height="6" />

        <v-alert v-if="!txLoading" :type="status ? 'success' : 'error'">{{
          status ? 'Success' : 'Failed'
        }}</v-alert>

Transaction Hash

            <mew-transform-hash :hash="txHash" />
            <mew-copy :copy-value="txHash" tooltip="Copy Me!" class="ml-2" />

          <mew-alert
            v-if="dialogAlert"
            hide-close-icon
            theme="error"
            title="Error"
            :description="dialogAlert"
          />

          <mew-button
            class="mt-6"
            title="Close"
            btn-size="xlarge"
            @click.native="dismiss"
          />

      </v-sheet>
    </v-dialog>

    <mew-module
      class="pt-6"
      :title="title.title"
      :has-elevation="true"
      :has-indicator="true"
    >
      <template #moduleBody>
        <mew-stepper
          :compact="$vuetify.breakpoint.smAndDown"
          :items="stepperItems"
          :on-step="currentStep"
        ></mew-stepper>

          <v-sheet color="transparent" max-width="600px" class="mx-auto py-10">
            <network-switch />
          </v-sheet>
          <mew-button
            btn-size="xlarge"
            title="Next"
            class="mx-auto display--block NextButton"
            @click.native="currentStep = 2"
          />

          <v-sheet color="transparent" max-width="600px" class="mx-auto py-10">
            <mew-address-select
              class="OfflineAddressInput"
              label="From Address"
              copy-tooltip="Copy"
              save-tooltip="Save Address"
              :show-copy="detailLength"
              :show-save="false"
              :items="addresses"
              :enable-save-address="false"
              :is-valid-address="detailLength"
              :address-value="fromAddress"
              :error-messages="fromAddressMessage"
              @input="setAddress"
            />
            <mew-expand-panel
              v-if="detailLength"
              is-toggle
              has-dividers
              :panel-items="exPanelStep2"
              class="mt-4 mb-10 swap-expend OfflineDetails"
            >
              <template #panelBody1>

{{ d.title }}

{{ d.value }}

                  <mew-transform-hash v-if="d.address" :hash="d.address" />

              </template>
            </mew-expand-panel>

              <mew-button
                class="mx-1 mb-3"
                title="Back"
                btn-size="xlarge"
                btn-style="outline"
                @click.native="handleBack"
              />
              <mew-button
                class="mx-1 mb-3 NextButton2"
                title="Next"
                btn-size="xlarge"
                :disabled="!detailLength"
                @click.native="currentStep = 3"
              />

            <mew-button
              v-if="detailLength"
              class="mt-2 display--block mx-auto DownloadButton"
              title="Export JSON file"
              btn-size="small"
              btn-style="transparent"
              :btn-link="fileLink"
              :download="exportFileName"
            />
          </v-sheet>

          <v-sheet color="transparent" max-width="600px" class="mx-auto py-10">
            <v-textarea
              outlined
              label="Signature"
              class="mb-1 SignatureInput"
              :value="signature"
              :error="signatureError"
              :error-messages="signatureMessage"
              @input="checkTx"
            ></v-textarea>
            <mew-alert
              v-for="alert in sortedAlerts"
              :key="alert.key"
              class="mb-3"
              hide-close-icon
              :theme="alert.severity"
              :title="alert.title"
              :description="alert.message"
            />
            <mew-expand-panel
              v-if="!signatureError && signature !== ''"
              is-toggle
              has-dividers
              :panel-items="exPanelStep3"
              class="mt-4 mb-10 swap-expend SignatureRawDetails"
            >
              <template #panelBody1>

                  <v-textarea readonly auto-grow :value="rawTransaction" />

              </template>
              <template #panelBody2>

{{ d.title }}

                    {{ d.value }}

                  <mew-transform-hash v-if="d.address" :hash="d.address" />

              </template>
            </mew-expand-panel>

              <mew-button
                class="mx-1 mb-3"
                title="Back"
                btn-size="xlarge"
                btn-style="outline"
                @click.native="handleBack"
              />
              <mew-button
                title="Confirm & Send"
                btn-size="xlarge"
                class="mx-1 mb-3 ConfirmSendButton"
                :disabled="!validTx || error"
                @click.native="sendTx"
              />

              <input
                ref="uploadSig"
                type="file"
                style="display: none"
                accept="json"
                @change="uploadFile"
              />
              <mew-button
                class="mt-2 display--block mx-auto"
                title="Upload JSON file"
                btn-size="small"
                btn-style="transparent"
                @click.native="$refs.uploadSig.click()"
              />

          </v-sheet>

      </template>
    </mew-module>

</template>


RWD search fix for homepage

RWD search fix for homepage

<template>

Find the right sporting clothes for all situations

              <input ref="search" v-model="search" @input="makeSearch" type="text" class="m0 px25 inline-flex fs-medium" placeholder="What are you looking for?">

          Top searches
          Shoes
          Bags
          Longsleeves
          Jackets

          <router-link :to="`/p/${product.sku}/${product.slug}/${product.sku}`">
            <product-tile :product="product" class="b bg-primary"/>
          </router-link>

</template>


fixed something neko broke

fixed something neko broke

<script>
  import Overlayable from '../../mixins/overlayable'
  import Themeable from '../../mixins/themeable'

  import ClickOutside from '../../directives/click-outside'
  import Resize from '../../directives/resize'
  import Touch from '../../directives/touch'

  export default {
    name: 'v-navigation-drawer',

    mixins: [Overlayable, Themeable],

    directives: {
      ClickOutside,
      Resize,
      Touch
    },

    data () {
      return {
        isActive: this.value,
        isBooted: false,
        isMobile: false,
        touchArea: {
          left: 0,
          right: 0
        }
      }
    },

    props: {
      absolute: Boolean,
      clipped: Boolean,
      disableRouteWatcher: Boolean,
      enableResizeWatcher: Boolean,
      height: String,
      floating: Boolean,
      miniVariant: Boolean,
      mobileBreakPoint: {
        type: Number,
        default: 1024
      },
      permanent: Boolean,
      persistent: Boolean,
      right: Boolean,
      temporary: Boolean,
      touchless: Boolean,
      value: { required: false }
    },

    computed: {
      calculatedHeight () {
        return this.height || '100%'
      },
      classes () {
        return {
          'navigation-drawer': true,
          'navigation-drawer--absolute': this.absolute,
          'navigation-drawer--clipped': this.clipped,
          'navigation-drawer--close': !this.isActive,
          'navigation-drawer--floating': this.floating,
          'navigation-drawer--is-booted': this.isBooted,
          'navigation-drawer--is-mobile': this.isMobile,
          'navigation-drawer--mini-variant': this.miniVariant,
          'navigation-drawer--open': this.isActive,
          'navigation-drawer--permanent': this.permanent,
          'navigation-drawer--persistent': this.persistent,
          'navigation-drawer--right': this.right,
          'navigation-drawer--temporary': this.temporary,
          'theme--dark': this.dark,
          'theme--light': this.light
        }
      },
      showOverlay () {
        return !this.permanent &&
          this.isActive &&
          (this.temporary || this.isMobile)
      }
    },

    watch: {
      $route () {
        if (!this.disableRouteWatcher) {
          this.isActive = !this.closeConditional()
        }
      },
      isActive (val) {
        this.$emit('input', val)
        this.showOverlay &&
          val &&
          this.genOverlay() ||
          this.removeOverlay()
        this.$el.scrollTop = 0
      },
      isMobile (val) {
        !val && this.removeOverlay()
      },
      permanent (val) {
        this.$emit('input', val)
      },
      value (val) {
        if (this.permanent) return
        if (val !== this.isActive) this.isActive = val
      }
    },

    mounted () {
      this.$vuetify.load(this.init)
    },

    methods: {
      init () {
        this.checkIfMobile()
        setTimeout(() => (this.isBooted = true), 0)

        if (this.permanent) this.isActive = true
        else if (this.isMobile) this.isActive = false
        else if (!this.value &&
          (this.persistent || this.temporary)
        ) this.isActive = false
        else this.isActive = true
      },
      checkIfMobile () {
        this.isMobile = window.innerWidth < parseInt(this.mobileBreakPoint)
      },
      closeConditional () {
        return !this.permanent && (this.temporary || this.isMobile)
      },
      onResize () {
        clearTimeout(this.resizeTimeout)

        if (!this.enableResizeWatcher ||
          this.permanent ||
          this.temporary
        ) return

        this.resizeTimeout = setTimeout(() => {
          this.checkIfMobile()
          this.isActive = !this.isMobile
        }, 200)
      },
      swipeRight (e) {
        if (this.isActive && !this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (!this.right &&
          e.touchstartX <= this.touchArea.left
        ) this.isActive = true
        else if (this.right && this.isActive) this.isActive = false
      },
      swipeLeft (e) {
        if (this.isActive && this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (this.right &&
          e.touchstartX >= this.touchArea.right
        ) this.isActive = true
        else if (!this.right && this.isActive) this.isActive = false
      },
      calculateTouchArea () {
        if (!this.$el.parentNode) return
        const parentRect = this.$el.parentNode.getBoundingClientRect()

        this.touchArea = {
          left: parentRect.left + 50,
          right: parentRect.right - 50
        }
      },
      genDirectives () {
        const directives = [
          {
            name: 'click-outside',
            value: this.closeConditional
          },
          {
            name: 'resize',
            value: this.onResize
          }
        ]

        !this.touchless && directives.push({
          name: 'touch',
          value: {
            parent: true,
            left: this.swipeLeft,
            right: this.swipeRight
          }
        })

        return directives
      }
    },

    render (h) {
      const data = {
        'class': this.classes,
        style: { height: this.calculatedHeight },
        directives: this.genDirectives(),
        on: Object.assign({}, {
          click: () => this.$emit('update:miniVariant', false)
        }, this.$listeners)
      }

      return h('aside', data, [
        this.$slots.default,
        h('div', { 'class': 'navigation-drawer__border' })
      ])
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_navigation-drawer.styl"></style>

<script>
  import Overlayable from '../../mixins/overlayable'
  import Themeable from '../../mixins/themeable'

  import ClickOutside from '../../directives/click-outside'
  import Resize from '../../directives/resize'
  import Touch from '../../directives/touch'

  export default {
    name: 'v-navigation-drawer',

    mixins: [Overlayable, Themeable],

    directives: {
      ClickOutside,
      Resize,
      Touch
    },

    data () {
      return {
        isActive: this.value,
        isBooted: false,
        isMobile: false,
        touchArea: {
          left: 0,
          right: 0
        }
      }
    },

    props: {
      absolute: Boolean,
      clipped: Boolean,
      disableRouteWatcher: Boolean,
      enableResizeWatcher: Boolean,
      height: String,
      floating: Boolean,
      miniVariant: Boolean,
      mobileBreakPoint: {
        type: Number,
        default: 1024
      },
      permanent: Boolean,
      persistent: Boolean,
      right: Boolean,
      temporary: Boolean,
      touchless: Boolean,
      value: { required: false }
    },

    computed: {
      calculatedHeight () {
        return this.height || '100%'
      },
      classes () {
        return {
          'navigation-drawer': true,
          'navigation-drawer--absolute': this.absolute,
          'navigation-drawer--clipped': this.clipped,
          'navigation-drawer--close': !this.isActive,
          'navigation-drawer--floating': this.floating,
          'navigation-drawer--is-booted': this.isBooted,
          'navigation-drawer--is-mobile': this.isMobile,
          'navigation-drawer--mini-variant': this.miniVariant,
          'navigation-drawer--open': this.isActive,
          'navigation-drawer--permanent': this.permanent,
          'navigation-drawer--persistent': this.persistent,
          'navigation-drawer--right': this.right,
          'navigation-drawer--temporary': this.temporary,
          'theme--dark': this.dark,
          'theme--light': this.light
        }
      },
      showOverlay () {
        return !this.permanent &&
          this.isActive &&
          (this.temporary || this.isMobile)
      }
    },

    watch: {
      $route () {
        if (!this.disableRouteWatcher) {
          this.isActive = !this.closeConditional()
        }
      },
      isActive (val) {
        this.$emit('input', val)
        this.showOverlay && val && this.genOverlay() || this.removeOverlay()
        this.$el.scrollTop = 0
      },
      isMobile (val) {
        !val && this.removeOverlay()
      },
      permanent (val) {
        this.$emit('input', val)
      },
      value (val) {
        if (this.permanent) return
        if (val !== this.isActive) this.isActive = val
      }
    },

    mounted () {
      this.$vuetify.load(this.init)
    },

    methods: {
      init () {
        this.checkIfMobile()
        setTimeout(() => (this.isBooted = true), 0)

        if (this.permanent) this.isActive = true
        else if (this.isMobile) this.isActive = false
        else if (!this.value &&
          (this.persistent || this.temporary)
        ) this.isActive = false

        this.isActive && this.genOverlay()
      },
      checkIfMobile () {
        this.isMobile = window.innerWidth < parseInt(this.mobileBreakPoint)
      },
      closeConditional () {
        return !this.permanent && (this.temporary || this.isMobile)
      },
      onResize () {
        clearTimeout(this.resizeTimeout)

        if (!this.enableResizeWatcher ||
          this.permanent ||
          this.temporary
        ) return

        this.resizeTimeout = setTimeout(() => {
          this.checkIfMobile()
          this.isActive = !this.isMobile
        }, 200)
      },
      swipeRight (e) {
        if (this.isActive && !this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (!this.right &&
          e.touchstartX <= this.touchArea.left
        ) this.isActive = true
        else if (this.right && this.isActive) this.isActive = false
      },
      swipeLeft (e) {
        if (this.isActive && this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (this.right &&
          e.touchstartX >= this.touchArea.right
        ) this.isActive = true
        else if (!this.right && this.isActive) this.isActive = false
      },
      calculateTouchArea () {
        if (!this.$el.parentNode) return
        const parentRect = this.$el.parentNode.getBoundingClientRect()

        this.touchArea = {
          left: parentRect.left + 50,
          right: parentRect.right - 50
        }
      },
      genDirectives () {
        const directives = [
          {
            name: 'click-outside',
            value: this.closeConditional
          },
          {
            name: 'resize',
            value: this.onResize
          }
        ]

        !this.touchless && directives.push({
          name: 'touch',
          value: {
            parent: true,
            left: this.swipeLeft,
            right: this.swipeRight
          }
        })

        return directives
      }
    },

    render (h) {
      const data = {
        'class': this.classes,
        style: { height: this.calculatedHeight },
        directives: this.genDirectives(),
        on: Object.assign({}, {
          click: () => this.$emit('update:miniVariant', false)
        }, this.$listeners)
      }

      return h('aside', data, [
        this.$slots.default,
        h('div', { 'class': 'navigation-drawer__border' })
      ])
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_navigation-drawer.styl"></style>


bug/navigation-drawer-listeners (#1364)

bug/navigation-drawer-listeners (#1364)

* Fixed bug where VNavigationDrawer would receive input events from child components

* no longer passing along listeners for VNavigationDrawer

<script>
  import Overlayable from '../../mixins/overlayable'
  import Themeable from '../../mixins/themeable'

  import ClickOutside from '../../directives/click-outside'
  import Resize from '../../directives/resize'
  import Touch from '../../directives/touch'

  export default {
    name: 'v-navigation-drawer',

    mixins: [Overlayable, Themeable],

    directives: {
      ClickOutside,
      Resize,
      Touch
    },

    data () {
      return {
        isActive: this.value,
        isBooted: false,
        isMobile: false,
        touchArea: {
          left: 0,
          right: 0
        }
      }
    },

    props: {
      absolute: Boolean,
      clipped: Boolean,
      disableRouteWatcher: Boolean,
      enableResizeWatcher: Boolean,
      height: String,
      floating: Boolean,
      miniVariant: Boolean,
      mobileBreakPoint: {
        type: Number,
        default: 1280
      },
      permanent: Boolean,
      persistent: Boolean,
      right: Boolean,
      temporary: Boolean,
      touchless: Boolean,
      value: { required: false }
    },

    computed: {
      calculatedHeight () {
        return this.height || '100%'
      },
      classes () {
        return {
          'navigation-drawer': true,
          'navigation-drawer--absolute': this.absolute,
          'navigation-drawer--clipped': this.clipped,
          'navigation-drawer--close': !this.isActive,
          'navigation-drawer--floating': this.floating,
          'navigation-drawer--is-booted': this.isBooted,
          'navigation-drawer--is-mobile': this.isMobile,
          'navigation-drawer--mini-variant': this.miniVariant,
          'navigation-drawer--open': this.isActive,
          'navigation-drawer--permanent': this.permanent,
          'navigation-drawer--persistent': this.persistent,
          'navigation-drawer--right': this.right,
          'navigation-drawer--temporary': this.temporary,
          'theme--dark': this.dark,
          'theme--light': this.light
        }
      },
      showOverlay () {
        return !this.permanent &&
          this.isActive &&
          (this.temporary || this.isMobile)
      }
    },

    watch: {
      $route () {
        if (!this.disableRouteWatcher) {
          this.isActive = !this.closeConditional()
        }
      },
      isActive (val) {
        this.$emit('input', val)
        this.showOverlay &&
          val &&
          this.genOverlay() ||
          this.removeOverlay()
        this.$el.scrollTop = 0
      },
      isMobile (val) {
        !val && this.removeOverlay()
      },
      permanent (val) {
        this.$emit('input', val)
      },
      value (val) {
        if (this.permanent) return
        if (val !== this.isActive) this.isActive = val
      }
    },

    mounted () {
      this.$vuetify.load(this.init)
    },

    methods: {
      init () {
        this.checkIfMobile()
        setTimeout(() => (this.isBooted = true), 0)

        if (this.permanent) this.isActive = true
        else if (this.isMobile) this.isActive = false
        else if (!this.value &&
          (this.persistent || this.temporary)
        ) this.isActive = false
        else this.isActive = true
      },
      checkIfMobile () {
        this.isMobile = window.innerWidth < parseInt(this.mobileBreakPoint)
      },
      closeConditional () {
        return !this.permanent && (this.temporary || this.isMobile)
      },
      onResize () {
        clearTimeout(this.resizeTimeout)

        if (!this.enableResizeWatcher ||
          this.permanent ||
          this.temporary
        ) return

        this.resizeTimeout = setTimeout(() => {
          this.checkIfMobile()
          this.isActive = !this.isMobile
        }, 200)
      },
      swipeRight (e) {
        if (this.isActive && !this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (!this.right &&
          e.touchstartX <= this.touchArea.left
        ) this.isActive = true
        else if (this.right && this.isActive) this.isActive = false
      },
      swipeLeft (e) {
        if (this.isActive && this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (this.right &&
          e.touchstartX >= this.touchArea.right
        ) this.isActive = true
        else if (!this.right && this.isActive) this.isActive = false
      },
      calculateTouchArea () {
        if (!this.$el.parentNode) return
        const parentRect = this.$el.parentNode.getBoundingClientRect()

        this.touchArea = {
          left: parentRect.left + 50,
          right: parentRect.right - 50
        }
      },
      genDirectives () {
        const directives = [
          {
            name: 'click-outside',
            value: this.closeConditional
          },
          {
            name: 'resize',
            value: this.onResize
          }
        ]

        !this.touchless && directives.push({
          name: 'touch',
          value: {
            parent: true,
            left: this.swipeLeft,
            right: this.swipeRight
          }
        })

        return directives
      }
    },

    render (h) {
      const data = {
        'class': this.classes,
        style: { height: this.calculatedHeight },
        directives: this.genDirectives(),
        on: Object.assign({}, {
          click: () => this.$emit('update:miniVariant', false)
        })
      }

      return h('aside', data, [
        this.$slots.default,
        h('div', { 'class': 'navigation-drawer__border' })
      ])
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_navigation-drawer.styl"></style>

<script>
  import Overlayable from '../../mixins/overlayable'
  import Themeable from '../../mixins/themeable'

  import ClickOutside from '../../directives/click-outside'
  import Resize from '../../directives/resize'
  import Touch from '../../directives/touch'

  export default {
    name: 'v-navigation-drawer',

    mixins: [Overlayable, Themeable],

    directives: {
      ClickOutside,
      Resize,
      Touch
    },

    data () {
      return {
        isActive: this.value,
        isBooted: false,
        isMobile: false,
        touchArea: {
          left: 0,
          right: 0
        }
      }
    },

    props: {
      absolute: Boolean,
      clipped: Boolean,
      disableRouteWatcher: Boolean,
      enableResizeWatcher: Boolean,
      height: String,
      floating: Boolean,
      miniVariant: Boolean,
      mobileBreakPoint: {
        type: Number,
        default: 1280
      },
      permanent: Boolean,
      persistent: Boolean,
      right: Boolean,
      temporary: Boolean,
      touchless: Boolean,
      value: { required: false }
    },

    computed: {
      calculatedHeight () {
        return this.height || '100%'
      },
      classes () {
        return {
          'navigation-drawer': true,
          'navigation-drawer--absolute': this.absolute,
          'navigation-drawer--clipped': this.clipped,
          'navigation-drawer--close': !this.isActive,
          'navigation-drawer--floating': this.floating,
          'navigation-drawer--is-booted': this.isBooted,
          'navigation-drawer--is-mobile': this.isMobile,
          'navigation-drawer--mini-variant': this.miniVariant,
          'navigation-drawer--open': this.isActive,
          'navigation-drawer--permanent': this.permanent,
          'navigation-drawer--persistent': this.persistent,
          'navigation-drawer--right': this.right,
          'navigation-drawer--temporary': this.temporary,
          'theme--dark': this.dark,
          'theme--light': this.light
        }
      },
      showOverlay () {
        return !this.permanent &&
          this.isActive &&
          (this.temporary || this.isMobile)
      }
    },

    watch: {
      $route () {
        if (!this.disableRouteWatcher) {
          this.isActive = !this.closeConditional()
        }
      },
      isActive (val) {
        this.$emit('input', val)
        this.showOverlay &&
          val &&
          this.genOverlay() ||
          this.removeOverlay()
        this.$el.scrollTop = 0
      },
      isMobile (val) {
        !val && this.removeOverlay()
      },
      permanent (val) {
        this.$emit('input', val)
      },
      value (val) {
        if (this.permanent) return
        if (val !== this.isActive) this.isActive = val
      }
    },

    mounted () {
      this.$vuetify.load(this.init)
    },

    methods: {
      init () {
        this.checkIfMobile()
        setTimeout(() => (this.isBooted = true), 0)

        if (this.permanent) this.isActive = true
        else if (this.isMobile) this.isActive = false
        else if (!this.value &&
          (this.persistent || this.temporary)
        ) this.isActive = false
        else this.isActive = true
      },
      checkIfMobile () {
        this.isMobile = window.innerWidth < parseInt(this.mobileBreakPoint)
      },
      closeConditional () {
        return !this.permanent && (this.temporary || this.isMobile)
      },
      onResize () {
        clearTimeout(this.resizeTimeout)

        if (!this.enableResizeWatcher ||
          this.permanent ||
          this.temporary
        ) return

        this.resizeTimeout = setTimeout(() => {
          this.checkIfMobile()
          this.isActive = !this.isMobile
        }, 200)
      },
      swipeRight (e) {
        if (this.isActive && !this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (!this.right &&
          e.touchstartX <= this.touchArea.left
        ) this.isActive = true
        else if (this.right && this.isActive) this.isActive = false
      },
      swipeLeft (e) {
        if (this.isActive && this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (this.right &&
          e.touchstartX >= this.touchArea.right
        ) this.isActive = true
        else if (!this.right && this.isActive) this.isActive = false
      },
      calculateTouchArea () {
        if (!this.$el.parentNode) return
        const parentRect = this.$el.parentNode.getBoundingClientRect()

        this.touchArea = {
          left: parentRect.left + 50,
          right: parentRect.right - 50
        }
      },
      genDirectives () {
        const directives = [
          {
            name: 'click-outside',
            value: this.closeConditional
          },
          {
            name: 'resize',
            value: this.onResize
          }
        ]

        !this.touchless && directives.push({
          name: 'touch',
          value: {
            parent: true,
            left: this.swipeLeft,
            right: this.swipeRight
          }
        })

        return directives
      }
    },

    render (h) {
      const data = {
        'class': this.classes,
        style: { height: this.calculatedHeight },
        directives: this.genDirectives(),
        on: Object.assign({}, {
          click: () => this.$emit('update:miniVariant', false)
        }, this.$listeners)
      }

      return h('aside', data, [
        this.$slots.default,
        h('div', { 'class': 'navigation-drawer__border' })
      ])
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_navigation-drawer.styl"></style>


docs(MdApp): Fixes link from md-app to md-content (#1252)

Was previously linked to md-drawer.

<example src="./examples/Normal.vue" />
<example src="./examples/Waterfall.vue" />
<example src="./examples/Reveal.vue" />
<example src="./examples/Flexible.vue" />
<example src="./examples/LastRowFixed.vue" />
<example src="./examples/Overlap.vue" />

<template>
  <page-container centered :title="$t('pages.app.title')">

      <i18n path="pages.app.intro.desc1" tag="p" />
      <i18n path="pages.app.intro.desc2" tag="p">
        md-app
      </i18n>
      <i18n path="pages.app.intro.desc3" tag="p" />
      <i18n path="pages.app.intro.desc4" tag="p">
        md-app
        md-app-toolbar
        md-app-drawer
        md-app-content
        md-toolbar
        md-drawer
        md-content
      </i18n>

{{ $t('pages.app.regular.title') }}

{{ $t('pages.app.regular.desc1') }}

      <i18n tag="note-block" tip path="pages.app.regular.desc2">
        md-app
      </i18n>

{{ $t('pages.app.fixed.title') }}

{{ $t('pages.app.fixed.desc') }}

{{ $t('pages.app.reveal.title') }}

{{ $t('pages.app.reveal.desc') }}

{{ $t('pages.app.flexible.title') }}

{{ $t('pages.app.flexible.desc') }}

{{ $t('pages.app.lastRow.title') }}

{{ $t('pages.app.lastRow.desc') }}

{{ $t('pages.app.overlap.title') }}

{{ $t('pages.app.overlap.desc') }}

      <api-item title="API - md-app">

{{ $t('pages.app.apiApp.desc') }}

md-app-toolbar: {{ $t('pages.app.apiApp.toolbar') }}

md-app-drawer: {{ $t('pages.app.apiApp.drawer') }}

md-app-content: {{ $t('pages.app.apiApp.content') }}

        <note-block alert>{{ $t('pages.app.apiApp.alert') }}</note-block>

{{ $t('pages.app.apiApp.propsDesc') }}:

        <api-table :headings="api.props.headings" :props="api.props.props" slot="props" />
      </api-item>

      <api-item title="API - md-app-toolbar">
        <i18n path="pages.app.apiToolbar.desc" tag="p">
          md-toolbar
          <router-link to="/components/toolbar">{{ $t('pages.app.apiToolbar.page') }}</router-link>
        </i18n>
      </api-item>

      <api-item title="API - md-app-drawer">
        <i18n path="pages.app.apiDrawer.desc" tag="p">
          md-drawer
          <router-link to="/components/drawer">{{ $t('pages.app.apiDrawer.page') }}</router-link>
        </i18n>
      </api-item>

      <api-item title="API - md-app-content">
        <i18n path="pages.app.apiContent.desc" tag="p">
          md-content
          <router-link to="/components/content">{{ $t('pages.app.apiContent.page') }}</router-link>
        </i18n>
      </api-item>

  </page-container>
</template>

<example src="./examples/Normal.vue" />
<example src="./examples/Waterfall.vue" />
<example src="./examples/Reveal.vue" />
<example src="./examples/Flexible.vue" />
<example src="./examples/LastRowFixed.vue" />
<example src="./examples/Overlap.vue" />

<template>
  <page-container centered :title="$t('pages.app.title')">

      <i18n path="pages.app.intro.desc1" tag="p" />
      <i18n path="pages.app.intro.desc2" tag="p">
        md-app
      </i18n>
      <i18n path="pages.app.intro.desc3" tag="p" />
      <i18n path="pages.app.intro.desc4" tag="p">
        md-app
        md-app-toolbar
        md-app-drawer
        md-app-content
        md-toolbar
        md-drawer
        md-content
      </i18n>

{{ $t('pages.app.regular.title') }}

{{ $t('pages.app.regular.desc1') }}

      <i18n tag="note-block" tip path="pages.app.regular.desc2">
        md-app
      </i18n>

{{ $t('pages.app.fixed.title') }}

{{ $t('pages.app.fixed.desc') }}

{{ $t('pages.app.reveal.title') }}

{{ $t('pages.app.reveal.desc') }}

{{ $t('pages.app.flexible.title') }}

{{ $t('pages.app.flexible.desc') }}

{{ $t('pages.app.lastRow.title') }}

{{ $t('pages.app.lastRow.desc') }}

{{ $t('pages.app.overlap.title') }}

{{ $t('pages.app.overlap.desc') }}

      <api-item title="API - md-app">

{{ $t('pages.app.apiApp.desc') }}

md-app-toolbar: {{ $t('pages.app.apiApp.toolbar') }}

md-app-drawer: {{ $t('pages.app.apiApp.drawer') }}

md-app-content: {{ $t('pages.app.apiApp.content') }}

        <note-block alert>{{ $t('pages.app.apiApp.alert') }}</note-block>

{{ $t('pages.app.apiApp.propsDesc') }}:

        <api-table :headings="api.props.headings" :props="api.props.props" slot="props" />
      </api-item>

      <api-item title="API - md-app-toolbar">
        <i18n path="pages.app.apiToolbar.desc" tag="p">
          md-toolbar
          <router-link to="/components/toolbar">{{ $t('pages.app.apiToolbar.page') }}</router-link>
        </i18n>
      </api-item>

      <api-item title="API - md-app-drawer">
        <i18n path="pages.app.apiDrawer.desc" tag="p">
          md-drawer
          <router-link to="/components/drawer">{{ $t('pages.app.apiDrawer.page') }}</router-link>
        </i18n>
      </api-item>

      <api-item title="API - md-app-content">
        <i18n path="pages.app.apiContent.desc" tag="p">
          md-content
          <router-link to="/components/drawer">{{ $t('pages.app.apiContent.page') }}</router-link>
        </i18n>
      </api-item>

  </page-container>
</template>


new title tag added

new title tag added

<template>

    <progress min="0" max="1" value="0" ref="progress"></progress>

      skip_previous
      play_circle_outline
      skip_next
      <audio ref="audioTag" :src="source" autoplay preload="none" @timeupdate="onTimeUpdateListener"></audio>
      {{ title }}

</template>

<template>

      skip_previous
      play_circle_outline
      skip_next
      <audio ref="audioTag" :src="source" autoplay preload="none" controls></audio>

</template>


Update i tags so they are not self closed

<template>

        {{group.numberProjects}}

        {{group.numberUsers}}

      {{fullPath}}

      {{group.description}}

    <group-folder v-if="group.isOpen && hasGroups" :groups="group.subGroups" :baseGroup="group" />

</template>


Close tag

Close tag

<template>

    <campaign-request v-if="showRequestForm"
      :config="requestConfig"
      :buttonText="config.button_text"
      :request-form="requestForm"
      :user-info="user"
      :user-form="userForm"
      :data="showRequestForm"
      :current-url="''"
      :campaignId="config.campaignId"
      :lawType="config.lawType"
      :extraText="config.requestExtraText"
      :hideNewsletterCheckbox="hideNewsletterCheckbox"
      :subscribeText="config.subscribe_text"
      :hasSubscription="config.hasSubscription"
      :publicbody="publicbody"
      :publicbodies="[publicbody]"
      :publicbodiesOptions="publicbodies"
      @publicBodyChanged="updatePublicBody"
      @detailfetched="detailFetched"
      @requestmade="requestMade"
      @userupdated="userUpdated"
      @tokenupdated="tokenUpdated"
      @close="requestFormClosed"
    ></campaign-request>

          Filter

        <input
          type="search"
          v-model="search"
          @keyup="searchObjects"
          :placeholder="i18n.search"
          class="form-control mb-4"
        />

DE

                  <input v-model="search" @keyup="searchObjects" placeholder="BY 1234" class="form-control text-center h4"/>

EG

          <CampaignListTag
            v-for="res in resolutions"
            :key="res"
            :active="res === resolution"
            :status="res"
            :isButton="true"
            @click="setResolutionFilter(res)"
          >

            {{ i18n.resolutions[res] }}
          </CampaignListTag>

          <CampaignListTag
            v-for="tag in config.tags"
            :key="tag"
            :active="currentTag === tag"
            @click="setTagFilter(tag)"
            :isButton="true"
          >
            #{{ tag }}
          </CampaignListTag>

        <transition-group name="list">
          <CampaignListItem
            v-for="object in objects"
            :key="object.id"
            :object="object"
            :currentTag="currentTag"
            :language="language"
            class="list-item"
            @filter="setTagFilter"
            @startRequest="startRequest"
          />

        </transition-group>

            <button @click="fetch" class="btn btn-light">{{ i18n.loadMore }}</button>

</template>


Sticky header

Sticky header

<template>

        Coin menu

        USD
        EUR

        {{ editButtonLabel }}

        <transition name="fade">

        </transition>
        Coin
        24h %
        Latest price

      <tablerow v-for="coin in coins" :key="coin.id" :coin="coin" :coins="coins" :currency="currency" :editMode="editMode" />

    <transition name="fade">
      <addcoin v-if="editMode" />
    </transition>

    <foot :refresh-date="refreshDate" />

</template>


fix: correct more HTML in masks table

fix: correct more HTML in masks table

see #230

<template lang="pug">
  v-card.mb-5
    v-table-overflow
      table.table
        caption.mt-3
          strong Mask legend
        thead
          tr.text-xs-left
            th Mask
            th Description
        tbody
          tr(
            v-for="(row, i) in masks"
            :key="i"
          )
            template(v-if="row.header")
              td
                strong {{ row.text }}
            template(v-else)
              td(v-for="(mask, i) in row" :key="i") {{ mask }}
</template>

<script>
  export default {
    data: () => ({
      masks: [
        { header: true, text: 'Masks' },
        ['#', 'Any digit'],
        ['A', 'Any capital letter'],
        ['a', 'Any small letter'],
        ['N', 'Any capital alphanumeric character'],
        ['n', 'Any small alphanumeric character'],
        ['X', 'Any special symbol (-!$%^&*()_+|~=`{}[]:";\'<>?,./\\) or space'],
        { header: true, text: 'Pre-made' },
        ['credit-card', '#### - #### - #### - ####'],
        ['date-with-time', '##/##/#### ##:##'],
        ['phone', '(###) ### - ####'],
        ['social', '###-##-####'],
        ['time', '##:##'],
        ['time-with-seconds', '##:##:##']
      ]
    })
  }
</script>

<template lang="pug">
  v-card.mb-5
    v-table-overflow
      table.table
        caption.mt-3
          strong Mask legend
        thead
          tr.text-xs-left
            th Mask
            th Description
        tbody
          tr(
            v-for="(row, i) in masks"
            :key="i"
          )
            td(
              v-for="(mask, i) in row"
              :key="i"
              v-html="mask"
            )
</template>


home content

home content

<template lang='jade'>

    #about
        shared-title(title='About Us')
            p DCTRL is a Collaborative Community inspired by the ideas behind Bitcoin, Decentralization, and the Open Source movement. This is a meetup where forward thinking people can hangout with like-minded individuals. We believe Cryptocurrency and the Open Source movement can create economically sustainable systems for communities globally and locally. The space is located Downtown Vancouver at
                a(href='http://maps.google.com/?q=436 w Pender, Vancouver, British Columbia') 436 West Pender Street
            p Check out our
                a(href='https://www.meetup.com/dctrlvan/') meetup page
                span . If you are interested in hosting an event please make a request to info@decentral.network
            p Have a question or concept you want to discuss? join our
                a(href='https://decentralslackvite.herokuapp.com/') Slack Chat.
            p You code? Then find us on
                a(href='https://github.com/DecentralVan/dctrlapp') github!
            h3 The App
            p The
                 a(href='https://github.com/DecentralVan/dctrlapp') dctrlapp
                 span  is community management software for helping communities self-govern themselves in a peer-to-peer way. All the code is open source including a web interface for interaction and information, a
                    a(href='https://github.com/decentralvan/eventstate') backend
                    span  for managing members, and a rfid fob system that controls access to commons property.

</template>

<script>

import SharedTitle from '../slotUtils/SharedTitle'

export default {
    components:{
        SharedTitle
    }
}

</script>

<style lang='stylus' scoped>

@import '../../styles/colours'

p
    font-size:1.3em
    color:white
    font-family: 'Open Sans', light, sans-serif;

a
    color: accent2

h3
    text-align: left
    color:accent1
    font-family: 'Open Sans', light, sans-serif;

</style>

<template lang='jade'>

#projects
    shared-title(title='DCTRL')

        h3 Links
        p Check out our
            a(href='https://www.meetup.com/dctrlvan/') meetup page
            span .
        p Have a question or concept you want to discuss, join our
            a(href='https://decentralslackvite.herokuapp.com/') Slack Chat.
        p You code? Then find us on
            a(href='https://github.com/DecentralVan/dctrlapp') github!
        h3 The App
        p The
             a(href='https://github.com/DecentralVan/dctrlapp') dctrlapp
             span  is community management software for helping communities self-govern themselves in a peer-to-peer way. All the code is open source including a web interface for interaction and information, a
                a(href='https://github.com/decentralvan/eventstate') backend
                span  for managing members, and a rfid fob system that controls access to commons property.

</template>

<script>

import SharedTitle from '../slotUtils/SharedTitle'

export default {
    components:{
        SharedTitle
    }
}

</script>

<style lang='stylus' scoped>

@import '../../styles/colours'

p
    font-size:1.3em
    color:white
    font-family: 'Open Sans', light, sans-serif;

a
    color: accent2

h3
    text-align: left
    color:accent1
    font-family: 'Open Sans', light, sans-serif;

</style>


Fix Pagination button display on 0 totalItems

<template>

    <template v-if="hasButtons">

        Previous

        Next

    </template>

      <template v-if="showFirstPage">

              1

          …

      </template>

          {{ page }}

      <template v-if="showLastPage">

          …

              {{ lastPage }}

      </template>

</template>


💚 show song count and improve tuning in song list

<template>

New Setlist «{{ setlist.title }}»

Edit Setlist «{{ setlist.title }}»

										Title *
										<input v-if="existing" v-model="setlist.title" class="form-input" id="title" type="text" placeholder="e.g. Sunday Service" disabled>
										<input v-else v-model="setlist.title" class="form-input" id="title" type="text" placeholder="e.g. Sunday Service">

A title is required.

A setlist with this title already exists on this day. Please change either the title or the day.

										Event Date {{ setlist.date }}
										<datepicker
											:value="setlist.date != '' ? (new Date(setlist.date)) : (new Date())"
											format="yyyy-MM-dd"
											wrapper-class="calendar-wrapper hide-lg"
											input-class="form-input"
											calendar-class="calendar"
											:typeable="true"
											:inline="true"
											:monday-first="true"
											:placeholder="'event date, e.g. ' + String((new Date()).toISOString()).slice(0,10)"
											:open-date="setlist.date != '' ? (new Date(setlist.date)) : (new Date())"
											name="setlistdate"
											@selected="updateDate"
										></datepicker>
										<input
											class="form-input show-lg"
											type="date"
											v-model="setlist.date"
											pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"
										/>

									Songs

												<ion-icon name="search-outline"></ion-icon>
												<input v-model="search" id="search" type="search" class="form-input" placeholder="search ..." />
												<button class="btn input-group-btn btn-link" @click="search = ''"><ion-icon name="close"></ion-icon></button>

												<ion-icon name="pricetag-outline"></ion-icon>
												<select v-model="filter" class="form-select filter" required>
													<option value="" disabled selected>tag ...</option>
													<option v-for="tag in tags" :key="tag.key" :value="tag.key">{{ tag.key }}</option>
												</select>
												<button class="btn input-group-btn btn-link" @click="filter = ''"><ion-icon name="close"></ion-icon></button>

												<ion-icon name="musical-note"></ion-icon>
												<select v-model="tuning" class="form-select filter" required>
													<option value="" disabled selected>tune ...</option>
													<option v-for="t in tunes" :key="t" :value="t">{{ t }}</option>
												</select>
												<button class="btn input-group-btn btn-link" @click="tuning = ''"><ion-icon name="close"></ion-icon></button>

											<input v-model="setlistSongs" :value="key" type="checkbox">
											 {{ fsong.title }} {{ fsong.tuning }}

												{{ fsong.subtitle }}

											<ion-icon name="musical-notes-outline" class="icon-4x"></ion-icon>

No Songs selected

Select some songs for this setlist

{{ setlist.songs.length }} selected

												<ion-icon name="reorder-four" class="px-2 mx-2 handle"></ion-icon>
												<button class="btn btn-secondary btn-sm btn-fw" @click.prevent="tuneDown(i)">
													<ion-icon name="arrow-back" class="icon-sm"></ion-icon>
												</button>

												<button class="btn btn-secondary btn-sm btn-fw" @click.prevent="tuneUp(i)">
													<ion-icon name="arrow-forward" class="icon-sm"></ion-icon>
												</button>

{{ songs[song.id].title }}

{{ songs[song.id].subtitle }}

													<button class="btn btn-link btn-action" @click="setlistSongs = setlistSongs.filter(function(k) {return k !== song.id})">
														<ion-icon name="close"></ion-icon>
													</button>

				<button class="btn btn-link btn-gray" aria-label="Cancel" @click.prevent="cancel">Cancel</button>
				<button class="btn btn-primary ml-2" @click="set">
					Create
					Update Setlist
				</button>

</template>

<template>

New Setlist «{{ setlist.title }}»

Edit Setlist «{{ setlist.title }}»

										Title *
										<input v-if="existing" v-model="setlist.title" class="form-input" id="title" type="text" placeholder="e.g. Sunday Service" disabled>
										<input v-else v-model="setlist.title" class="form-input" id="title" type="text" placeholder="e.g. Sunday Service">

A title is required.

A setlist with this title already exists on this day. Please change either the title or the day.

										Event Date {{ setlist.date }}
										<datepicker
											:value="setlist.date != '' ? (new Date(setlist.date)) : (new Date())"
											format="yyyy-MM-dd"
											wrapper-class="calendar-wrapper hide-lg"
											input-class="form-input"
											calendar-class="calendar"
											:typeable="true"
											:inline="true"
											:monday-first="true"
											:placeholder="'event date, e.g. ' + String((new Date()).toISOString()).slice(0,10)"
											:open-date="setlist.date != '' ? (new Date(setlist.date)) : (new Date())"
											name="setlistdate"
											@selected="updateDate"
										></datepicker>
										<input
											class="form-input show-lg"
											type="date"
											v-model="setlist.date"
											pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"
										/>

									Songs

												<ion-icon name="search-outline"></ion-icon>
												<input v-model="search" id="search" type="search" class="form-input" placeholder="search ..." />
												<button class="btn input-group-btn btn-link" @click="search = ''"><ion-icon name="close"></ion-icon></button>

												<ion-icon name="pricetag-outline"></ion-icon>
												<select v-model="filter" class="form-select filter" required>
													<option value="" disabled selected>tag ...</option>
													<option v-for="tag in tags" :key="tag.key" :value="tag.key">{{ tag.key }}</option>
												</select>
												<button class="btn input-group-btn btn-link" @click="filter = ''"><ion-icon name="close"></ion-icon></button>

												<ion-icon name="musical-note"></ion-icon>
												<select v-model="tuning" class="form-select filter" required>
													<option value="" disabled selected>tune ...</option>
													<option v-for="t in tunes" :key="t" :value="t">{{ t }}</option>
												</select>
												<button class="btn input-group-btn btn-link" @click="tuning = ''"><ion-icon name="close"></ion-icon></button>

											<input v-model="setlistSongs" :value="key" type="checkbox">
											 {{ fsong.title }} [{{ fsong.tuning }}]

												{{ fsong.subtitle }}

											<ion-icon name="musical-notes-outline" class="icon-4x"></ion-icon>

No Songs selected

Select some songs for this setlist

Selection

												<ion-icon name="reorder-four" class="px-2 mx-2 handle"></ion-icon>
												<button class="btn btn-secondary btn-sm btn-fw" @click.prevent="tuneDown(i)">
													<ion-icon name="arrow-back" class="icon-sm"></ion-icon>
												</button>

												<button class="btn btn-secondary btn-sm btn-fw" @click.prevent="tuneUp(i)">
													<ion-icon name="arrow-forward" class="icon-sm"></ion-icon>
												</button>

{{ songs[song.id].title }}

{{ songs[song.id].subtitle }}

													<button class="btn btn-link btn-action" @click="setlistSongs = setlistSongs.filter(function(k) {return k !== song.id})">
														<ion-icon name="close"></ion-icon>
													</button>

				<button class="btn btn-link btn-gray" aria-label="Cancel" @click.prevent="cancel">Cancel</button>
				<button class="btn btn-primary ml-2" @click="set">
					Create
					Update Setlist
				</button>

</template>