<commit_before><template>
  <div id="sources">
    <div v-for="(source,index) in getSources" :key="index" class="source col-sm-12">
      <div class="inputs-container">
        <div v-for="prop in Object.keys(source)" class="input-group">
          <span class="input-group-addon input-sm">{{prop}}</span>
          <input :class="{ 'form-control input-sm': true, 'validate-danger': errors.has(prop + index) }" :value="source[prop]" @input="setSourceProp(index, prop, $event.target.value)" type="text" :id="prop + index" v-validate="sourceValidationRules(prop, index)" :name="prop + index"/>
          <div v-show="errors.has(prop + index)" class="row help validate-danger">
            {{ errors.first(prop + index)}}
          </div>
        </div>
      </div>
      <button v-show="getSources.length > 1" type="button" class="btn btn-danger btn-sm" @click="removeSource(index)">
        <span class="glyphicon glyphicon-minus"/>
      </button>
    </div>
    <div class="button-container">
      <button type="button" class="add-source btn btn-primary btn-sm" @click="addSource()">
        <span class="glyphicon glyphicon-plus"/>Add source
      </button>
    </div>
  </div>
</template>
<script>
import {
  mapMutations,
  mapState,
  mapGetters
} from 'vuex'
import SideNav from './SideNav'
import AsyncComputed from 'vue-async-computed'
import ValidationRules from '../mixins/ValidationRules'
import VeeValidate from 'vee-validate'
import Vue from 'vue'
Vue.use(AsyncComputed)
export default {
  name: 'sources',
  mixins: [ValidationRules],
  data() {
    return {
      sources: []
    }
  },
  props: ['setProperty', 'getProperty', 'getPropertyGivenHotId'],
  extends: SideNav,
  computed: {
    ...mapGetters(['getActiveTab']),
    regexForPath() {
      // no ../ or nulls or absolute paths allowed
      return /^(([.](?![.])|[^/.:])+[/]*)+$/
    }
  },
  asyncComputed: {
    getSources: {
      async get() {
        let tab = this.getActiveTab
        let sources = await this.getSourcesFromTab(tab)
        return sources
      },
      watch() {
        return this.sources
      }
    }
  },
  methods: {
    ...mapMutations([
      'pushPackageProperty'
    ]),
    removeSource: function(index) {
      let sources = this.getProperty('sources')
      sources.splice(index, 1)
      this.setProperty('sources', sources)
      this.sources = sources
    },
    addSource: function() {
      let sources = this.getProperty('sources') || []
      sources.push(this.emptySource())
      this.setProperty('sources', sources)
      this.sources = sources
    },
    emptySource: function() {
      return {'title': '', 'path': '', 'email': ''}
    },
    getSourcesFromTab: async function(tab) {
      let hotId = await this.waitForHotIdFromTabId(tab)
      let sources = this.getPropertyGivenHotId('sources', hotId)
      return sources
    },
    initSources: async function(tab) {
      let sources = await this.getSourcesFromTab(tab)
      if (!sources) {
        const vueAddSource = this.addSource
        _.delay(function() {
          vueAddSource()
        }, 100)
      }
    },
    setSourceProp: function(index, prop, value) {
      this.setProperty(`sources[${index}][${prop}]`, value)
      let sources = this.getProperty('sources') || []
      this.sources = sources
      if (prop === 'path') {
        this.validateUrlOrPath(index, value)
      }
    },
    validateUrlOrPath: async function(index, value) {
      let prop = 'path'
      let field = `${prop}${index}`
      try {
        let hasValidUrl = await this.validateUrl(field, value)
        let hasValidPath = await this.validatePath(field, value)
        this.$validator.detach(field)
        if (!hasValidUrl && !hasValidPath) {
          this.$validator.errors.add({field: field, msg: 'The path field must be a valid email or path.'})
        }
      } catch (err) {
        console.log('Problem with validation', err)
      }
    },
    validateUrl: async function(field, value) {
      return this.validate(field, value, {url: true})
    },
    validatePath: async function(field, value) {
      return this.validate(field, value, {
        regex: this.regexForPath
      })
    },
    validate: async function(field, value, rulesObject) {
      let isValid = true
      // ensure there are no other fields by this name
      this.$validator.detach(field)
      await this.$validator.attach({
        name: field,
        rules: rulesObject
      })
      isValid = await this.$validator.validate(field, value)
      this.$validator.detach(field)
      return isValid
    },
    sourceValidationRules: function(prop, index) {
      switch (prop) {
        case 'email':
          return 'email'
        case 'title':
          return 'required'
        default:
          return ''
      }
    }
  },
  mounted: function() {
    let tab = this.getActiveTab
    this.initSources(tab)
  },
  watch: {
    getActiveTab: function(tab) {
      this.initSources(tab)
    }
  }
}
</script>
<style lang="styl" scoped>
@import '~static/css/sources'
</style>
<style lang="styl" scoped>
@import '~static/css/validationrules'
</style>
<commit_msg>Fixes #290. Changed format of vee-validate rule to ensure urls respect protocol.<commit_after><template>
  <div id="sources">
    <div v-for="(source,index) in getSources" :key="index" class="source col-sm-12">
      <div class="inputs-container">
        <div v-for="prop in Object.keys(source)" class="input-group">
          <span class="input-group-addon input-sm">{{prop}}</span>
          <input :class="{ 'form-control input-sm': true, 'validate-danger': errors.has(prop + index) }" :value="source[prop]" @input="setSourceProp(index, prop, $event.target.value)" type="text" :id="prop + index" v-validate="sourceValidationRules(prop, index)" :name="prop + index"/>
          <div v-show="errors.has(prop + index)" class="row help validate-danger">
            {{ errors.first(prop + index)}}
          </div>
        </div>
      </div>
      <button v-show="getSources.length > 1" type="button" class="btn btn-danger btn-sm" @click="removeSource(index)">
        <span class="glyphicon glyphicon-minus"/>
      </button>
    </div>
    <div class="button-container">
      <button type="button" class="add-source btn btn-primary btn-sm" @click="addSource()">
        <span class="glyphicon glyphicon-plus"/>Add source
      </button>
    </div>
  </div>
</template>
<script>
import {
  mapMutations,
  mapState,
  mapGetters
} from 'vuex'
import SideNav from './SideNav'
import AsyncComputed from 'vue-async-computed'
import ValidationRules from '../mixins/ValidationRules'
import VeeValidate from 'vee-validate'
import Vue from 'vue'
Vue.use(AsyncComputed)
export default {
  name: 'sources',
  mixins: [ValidationRules],
  data() {
    return {
      sources: []
    }
  },
  props: ['setProperty', 'getProperty', 'getPropertyGivenHotId'],
  extends: SideNav,
  computed: {
    ...mapGetters(['getActiveTab']),
    regexForPath() {
      // no ../ or nulls or absolute paths allowed
      return /^(([.](?![.])|[^/.:]+)+[/]*)+$/
    }
  },
  asyncComputed: {
    getSources: {
      async get() {
        let tab = this.getActiveTab
        let sources = await this.getSourcesFromTab(tab)
        return sources
      },
      watch() {
        return this.sources
      }
    }
  },
  methods: {
    ...mapMutations([
      'pushPackageProperty'
    ]),
    removeSource: function(index) {
      let sources = this.getProperty('sources')
      sources.splice(index, 1)
      this.setProperty('sources', sources)
      this.sources = sources
    },
    addSource: function() {
      let sources = this.getProperty('sources') || []
      sources.push(this.emptySource())
      this.setProperty('sources', sources)
      this.sources = sources
    },
    emptySource: function() {
      return {'title': '', 'path': '', 'email': ''}
    },
    getSourcesFromTab: async function(tab) {
      let hotId = await this.waitForHotIdFromTabId(tab)
      let sources = this.getPropertyGivenHotId('sources', hotId)
      return sources
    },
    initSources: async function(tab) {
      let sources = await this.getSourcesFromTab(tab)
      if (!sources) {
        const vueAddSource = this.addSource
        _.delay(function() {
          vueAddSource()
        }, 100)
      }
    },
    setSourceProp: function(index, prop, value) {
      this.setProperty(`sources[${index}][${prop}]`, value)
      let sources = this.getProperty('sources') || []
      this.sources = sources
      if (prop === 'path') {
        this.validateUrlOrPath(index, value)
      }
    },
    validateUrlOrPath: async function(index, value) {
      let prop = 'path'
      let field = `${prop}${index}`
      try {
        let hasValidUrl = await this.validateUrl(field, value)
        let hasValidPath = await this.validatePath(field, value)
        this.$validator.detach(field)
        if (!hasValidUrl && !hasValidPath) {
          this.$validator.errors.add({field: field, msg: 'The path field must be a valid email or path.'})
        }
      } catch (err) {
        console.log('Problem with validation', err)
      }
    },
    validateUrl: async function(field, value) {
      // keep url:true as string for validation to work correctly
      return this.validate(field, value, 'url:true')
    },
    validatePath: async function(field, value) {
      return this.validate(field, value, {
        regex: this.regexForPath
      })
    },
    validate: async function(field, value, rules) {
      let isValid = true
      // ensure there are no other fields by this name
      this.$validator.detach(field)
      await this.$validator.attach({
        name: field,
        rules: rules
      })
      isValid = await this.$validator.validate(field, value)
      this.$validator.detach(field)
      // console.log(isValid)
      return isValid
    },
    sourceValidationRules: function(prop, index) {
      switch (prop) {
        case 'email':
          return 'email'
        case 'title':
          return 'required'
        default:
          return ''
      }
    }
  },
  mounted: function() {
    let tab = this.getActiveTab
    this.initSources(tab)
  },
  watch: {
    getActiveTab: function(tab) {
      this.initSources(tab)
    }
  }
}
</script>
<style lang="styl" scoped>
@import '~static/css/sources'
</style>
<style lang="styl" scoped>
@import '~static/css/validationrules'
</style>

{'fraction_non_alphanumeric': 0.14908322933666124, 'fraction_numerical': 0.0016319477776711145, 'mean_word_length': 2.6644389729159337, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1666352', 'n_tokens_mistral': 3181, 'n_tokens_neox': 3157, 'n_words': 730}