<commit_before>'use strict'

const _ = require('lodash')
const _h = require('../_helpers')

exports.handler = async (event, context) => {
  if (event.source === 'aws.events') { return _h.json({ message: 'OK' }) }

  if (!event.Records && event.Records.length === 0) { return }

  for (var i = 0; i < event.Records.length; i++) {
    const data = JSON.parse(event.Records[i].body)
    const entityId = _h.strToId(data.entity)
    const db = await _h.db(data.account)

    if (data.dt) {
      const e = await db.collection('entity').findOne({ _id: entityId, aggregated: { $gte: new Date(data.dt) } }, { projection: { _id: false, aggregated: true } })
      if (e) {
        console.log('Entity', entityId, 'already aggregated at', e.aggregated)
        continue
      }
    }

    const properties = await db.collection('property').find({ entity: entityId, deleted: { $exists: false } }).toArray()

    if (properties.find(x => x.type === '_deleted')) {
      const deleteResponse = await db.collection('entity').deleteOne({ _id: entityId })
      console.log('Entity', entityId, 'deleted')
      continue
    }

    let entity = {
      aggregated: new Date()
    }

    for (var n = 0; n < properties.length; n++) {
      const prop = properties[n]
      let cleanProp = _.omit(prop, ['entity', 'type', 'created', 'search', 'public'])

      if (prop.reference && ['_viewer', '_expander', '_editor', '_owner'].includes(prop.type)) {
        if (!_.has(entity, 'access')) {
          _.set(entity, 'access', [])
        }
        entity.access.push(prop.reference)
      }

      if (!_.has(entity, ['private', prop.type])) {
        _.set(entity, ['private', prop.type], [])
      }

      if (prop.date) {
        const d = new Date(prop.date)
        cleanProp = { ...cleanProp, string: d.toISOString().substring(0, 9) }
      }

      if (prop.reference) {
        const referenceEntities = await db.collection('entity').findOne({ _id: prop.reference }, { projection: { 'private.name': true }})

        if (_.has(referenceEntities, 'private.name')) {
          cleanProp = referenceEntities.private.name.map(x => {
            return { ...cleanProp, ...x }
          })
        } else {
          cleanProp = { ...cleanProp, string: prop.reference }
        }
      }

      if (prop.formula) {
        const formulaResult = await formula(prop.formula, entityId, db)
        cleanProp = {...cleanProp, ...formulaResult}
      }

      if (!Array.isArray(cleanProp)) {
        cleanProp = [cleanProp]
      }
      entity.private[prop.type] = [...entity.private[prop.type], ...cleanProp]

      if (prop.public) {
        if (!_.has(entity, ['public', prop.type])) {
          _.set(entity, ['public', prop.type], [])
        }

        entity.public[prop.type] = [...entity.public[prop.type], ...cleanProp]
      }
    }

    const replaceResponse = await db.collection('entity').replaceOne({ _id: entityId }, entity)

    const referrers = await db.collection('property').aggregate([
      { $match: { reference: entityId, deleted: { $exists: false } } },
      { $group: { _id: '$entity' } }
    ]).toArray()

    const dt = data.dt ? new Date(data.dt) : entity.aggregated

    for (var i = 0; i < referrers.length; i++) {
      await _h.addEntityAggregateSqs(context, data.account, referrers[i]._id.toString(), dt)
    }

    console.log('Entity', entityId, 'updated and added', referrers.length, 'entities to SQS')
  }
}

const formula = async (str, entityId, db) => {
  let func = formulaFunction(str)
  let data = formulaContent(str)

  if (!['CONCAT', 'COUNT', 'SUM', 'AVG'].includes(func)) {
    return { string: str }
  }

  if (data.includes('(') || data.includes(')')) {
    const f = await formula(data)
    data = f.string || f.integer || f.decimal || ''
  }

  if (func === null) {
    return { string: data }
  }

  const dataArray = data.split(',')
  let valueArray = []

  for (let i = 0; i < dataArray.length; i++) {
    const value = await formulaField(dataArray[i], entityId, db)
    if (value !== null) {
      valueArray.push(value)
    }
  }

  switch (func) {
    case 'CONCAT':
      return { string: valueArray.join('') }
      break
    case 'COUNT':
      return { integer: valueArray.length }
      break
    case 'SUM':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) }
      break
    case 'SUBTRACT':
      return { decimal: valueArray.reduce((a, b) => a - b, 0) + (a[0] * 2) }
      break
    case 'AVERAGE':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) / arr.length }
      break
    case 'MIN':
      return { decimal: Math.min(valueArray) }
      break
    case 'MAX':
      return { decimal: Math.max(valueArray) }
      break
  }
}

const formulaFunction = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return null
  } else {
    return str.substring(0, str.indexOf('(')).toUpperCase()
  }
}

const formulaContent = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return str
  } else {
    return str.substring(str.indexOf('(') + 1, str.lastIndexOf(')'))
  }
}

const formulaField = async (str, entityId, db) => {
  str = str.trim()

  if ((str.startsWith("'") || str.startsWith('"')) && (str.endsWith("'") || str.endsWith('"'))) {
    return str.substring(1, str.length - 1)
  }

  let result

  switch (str.split('.').length) {
    case 1:
      const config = _.set({}, ['projection', `private.${str}.string`], true)
      const e = await db.collection('entity').findOne({ _id: entityId }, config)
      result = _.get(e, ['private', str, 0, 'string'], '')
      break
    default:
      result = null
  }

  return result
}
<commit_msg>Log out account<commit_after>'use strict'

const _ = require('lodash')
const _h = require('../_helpers')

exports.handler = async (event, context) => {
  if (event.source === 'aws.events') { return _h.json({ message: 'OK' }) }

  if (!event.Records && event.Records.length === 0) { return }

  for (var i = 0; i < event.Records.length; i++) {
    const data = JSON.parse(event.Records[i].body)
    const entityId = _h.strToId(data.entity)
    const db = await _h.db(data.account)

    if (data.dt) {
      const e = await db.collection('entity').findOne({ _id: entityId, aggregated: { $gte: new Date(data.dt) } }, { projection: { _id: false, aggregated: true } })
      if (e) {
        console.log('Entity', entityId, '@', data.account, 'already aggregated at', e.aggregated)
        continue
      }
    }

    const properties = await db.collection('property').find({ entity: entityId, deleted: { $exists: false } }).toArray()

    if (properties.find(x => x.type === '_deleted')) {
      const deleteResponse = await db.collection('entity').deleteOne({ _id: entityId })
      console.log('Entity', entityId, '@', data.account, 'deleted')
      continue
    }

    let entity = {
      aggregated: new Date()
    }

    for (var n = 0; n < properties.length; n++) {
      const prop = properties[n]
      let cleanProp = _.omit(prop, ['entity', 'type', 'created', 'search', 'public'])

      if (prop.reference && ['_viewer', '_expander', '_editor', '_owner'].includes(prop.type)) {
        if (!_.has(entity, 'access')) {
          _.set(entity, 'access', [])
        }
        entity.access.push(prop.reference)
      }

      if (!_.has(entity, ['private', prop.type])) {
        _.set(entity, ['private', prop.type], [])
      }

      if (prop.date) {
        const d = new Date(prop.date)
        cleanProp = { ...cleanProp, string: d.toISOString().substring(0, 9) }
      }

      if (prop.reference) {
        const referenceEntities = await db.collection('entity').findOne({ _id: prop.reference }, { projection: { 'private.name': true }})

        if (_.has(referenceEntities, 'private.name')) {
          cleanProp = referenceEntities.private.name.map(x => {
            return { ...cleanProp, ...x }
          })
        } else {
          cleanProp = { ...cleanProp, string: prop.reference }
        }
      }

      if (prop.formula) {
        const formulaResult = await formula(prop.formula, entityId, db)
        cleanProp = {...cleanProp, ...formulaResult}
      }

      if (!Array.isArray(cleanProp)) {
        cleanProp = [cleanProp]
      }
      entity.private[prop.type] = [...entity.private[prop.type], ...cleanProp]

      if (prop.public) {
        if (!_.has(entity, ['public', prop.type])) {
          _.set(entity, ['public', prop.type], [])
        }

        entity.public[prop.type] = [...entity.public[prop.type], ...cleanProp]
      }
    }

    const replaceResponse = await db.collection('entity').replaceOne({ _id: entityId }, entity)

    const referrers = await db.collection('property').aggregate([
      { $match: { reference: entityId, deleted: { $exists: false } } },
      { $group: { _id: '$entity' } }
    ]).toArray()

    const dt = data.dt ? new Date(data.dt) : entity.aggregated

    for (var i = 0; i < referrers.length; i++) {
      await _h.addEntityAggregateSqs(context, data.account, referrers[i]._id.toString(), dt)
    }

    console.log('Entity', entityId, '@', data.account, 'updated and added', referrers.length, 'entities to SQS')
  }
}

const formula = async (str, entityId, db) => {
  let func = formulaFunction(str)
  let data = formulaContent(str)

  if (!['CONCAT', 'COUNT', 'SUM', 'AVG'].includes(func)) {
    return { string: str }
  }

  if (data.includes('(') || data.includes(')')) {
    const f = await formula(data)
    data = f.string || f.integer || f.decimal || ''
  }

  if (func === null) {
    return { string: data }
  }

  const dataArray = data.split(',')
  let valueArray = []

  for (let i = 0; i < dataArray.length; i++) {
    const value = await formulaField(dataArray[i], entityId, db)
    if (value !== null) {
      valueArray.push(value)
    }
  }

  switch (func) {
    case 'CONCAT':
      return { string: valueArray.join('') }
      break
    case 'COUNT':
      return { integer: valueArray.length }
      break
    case 'SUM':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) }
      break
    case 'SUBTRACT':
      return { decimal: valueArray.reduce((a, b) => a - b, 0) + (a[0] * 2) }
      break
    case 'AVERAGE':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) / arr.length }
      break
    case 'MIN':
      return { decimal: Math.min(valueArray) }
      break
    case 'MAX':
      return { decimal: Math.max(valueArray) }
      break
  }
}

const formulaFunction = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return null
  } else {
    return str.substring(0, str.indexOf('(')).toUpperCase()
  }
}

const formulaContent = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return str
  } else {
    return str.substring(str.indexOf('(') + 1, str.lastIndexOf(')'))
  }
}

const formulaField = async (str, entityId, db) => {
  str = str.trim()

  if ((str.startsWith("'") || str.startsWith('"')) && (str.endsWith("'") || str.endsWith('"'))) {
    return str.substring(1, str.length - 1)
  }

  let result

  switch (str.split('.').length) {
    case 1:
      const config = _.set({}, ['projection', `private.${str}.string`], true)
      const e = await db.collection('entity').findOne({ _id: entityId }, config)
      result = _.get(e, ['private', str, 0, 'string'], '')
      break
    default:
      result = null
  }

  return result
}
<|endoftext|>


<commit_before>'use strict'

const _ = require('lodash')
const _h = require('../_helpers')

exports.handler = async (event, context) => {
  if (event.source === 'aws.events') { return _h.json({ message: 'OK' }) }

  if (!event.Records && event.Records.length === 0) { return }

  for (var i = 0; i < event.Records.length; i++) {
    const data = JSON.parse(event.Records[i].body)
    const entityId = _h.strToId(data.entity)
    const db = await _h.db(data.account)

    const entity = await db.collection('entity').findOne({ _id: entityId }, { projection: { _id: false, aggregated: true, 'private.name': true } })

    if (entity && entity.aggregated && data.dt && entity.aggregated >= new Date(data.dt)) {
      console.log('Entity', entityId, '@', data.account, 'already aggregated at', entity.aggregated)
      continue
    }

    const properties = await db.collection('property').find({ entity: entityId, deleted: { $exists: false } }).toArray()

    if (properties.find(x => x.type === '_deleted')) {
      const deleteResponse = await db.collection('entity').deleteOne({ _id: entityId })
      console.log('Entity', entityId, '@', data.account, 'deleted')
      continue
    }

    let newEntity = {
      aggregated: new Date()
    }

    for (var n = 0; n < properties.length; n++) {
      const prop = properties[n]
      let cleanProp = _.omit(prop, ['entity', 'type', 'created', 'search', 'public'])

      if (prop.reference && ['_viewer', '_expander', '_editor', '_owner'].includes(prop.type)) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push(prop.reference)
      }

      if (prop.type === '_public' && prop.boolean === true) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push('public')
      }

      if (!_.has(newEntity, ['private', prop.type])) {
        _.set(newEntity, ['private', prop.type], [])
      }

      if (prop.date) {
        const d = new Date(prop.date)
        cleanProp = { ...cleanProp, string: d.toISOString().substring(0, 9) }
      }

      if (prop.reference) {
        const referenceEntities = await db.collection('entity').findOne({ _id: prop.reference }, { projection: { 'private.name': true }})

        if (_.has(referenceEntities, 'private.name')) {
          cleanProp = referenceEntities.private.name.map(x => {
            return { ...cleanProp, ...x }
          })
        } else {
          cleanProp = { ...cleanProp, string: prop.reference.toString() }
        }
      }

      if (prop.formula) {
        const formulaResult = await formula(prop.formula, entityId, db)
        cleanProp = {...cleanProp, ...formulaResult}
      }

      if (!Array.isArray(cleanProp)) {
        cleanProp = [cleanProp]
      }
      newEntity.private[prop.type] = [...newEntity.private[prop.type], ...cleanProp]

      if (prop.public) {
        if (!_.has(newEntity, ['public', prop.type])) {
          _.set(newEntity, ['public', prop.type], [])
        }

        newEntity.public[prop.type] = [...newEntity.public[prop.type], ...cleanProp]
      }
    }

    const replaceResponse = await db.collection('entity').replaceOne({ _id: entityId }, newEntity)

    const name = _.get(entity, 'private.name', []).map(x => x.string || '')
    const newName = _.get(newEntity, 'private.name', []).map(x => x.string || '')

    if (!_.isEqual(_.sortBy(name), _.sortBy(newName))) {
      const referrers = await db.collection('property').aggregate([
        { $match: { reference: entityId, deleted: { $exists: false } } },
        { $group: { _id: '$entity' } }
      ]).toArray()

      const dt = data.dt ? new Date(data.dt) : newEntity.aggregated

      for (var i = 0; i < referrers.length; i++) {
        await _h.addEntityAggregateSqs(context, data.account, referrers[i]._id.toString(), dt)
      }

      console.log('Entity', entityId, '@', data.account, 'updated and added', referrers.length, 'entities to SQS')
    } else {
      console.log('Entity', entityId, '@', data.account, 'updated')
    }
  }
}

const formula = async (str, entityId, db) => {
  let func = formulaFunction(str)
  let data = formulaContent(str)

  if (!['CONCAT', 'COUNT', 'SUM', 'SUBTRACT', 'AVERAGE', 'MIN', 'MAX'].includes(func)) {
    return { string: str }
  }

  const dataArray = data.split(',')
  let valueArray = []

  for (let i = 0; i < dataArray.length; i++) {
    const value = await formulaField(dataArray[i], entityId, db)

    if (value) {
      valueArray = [...valueArray, ...value]
    }
  }

  valueArray = formulaValue(valueArray)

  switch (func) {
    case 'CONCAT':
      return { string: valueArray.join('') }
      break
    case 'COUNT':
      return { integer: valueArray.length }
      break
    case 'SUM':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) }
      break
    case 'SUBTRACT':
      return { decimal: valueArray.reduce((a, b) => a - b, 0) + (valueArray[0] * 2) }
      break
    case 'AVERAGE':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) / valueArray.length }
      break
    case 'MIN':
      return { decimal: Math.min(...valueArray) }
      break
    case 'MAX':
      return { decimal: Math.max(...valueArray) }
      break
  }
}

const formulaFunction = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return null
  } else {
    return str.substring(0, str.indexOf('(')).toUpperCase()
  }
}

const formulaContent = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return str
  } else {
    return str.substring(str.indexOf('(') + 1, str.lastIndexOf(')'))
  }
}

const formulaField = async (str, entityId, db) => {
  str = str.trim()

  if ((str.startsWith("'") || str.startsWith('"')) && (str.endsWith("'") || str.endsWith('"'))) {
    return [{
      string: str.substring(1, str.length - 1)
    }]
  }

  if (parseFloat(str).toString() === str) {
    return [{
      decimal: parseFloat(str)
    }]
  }

  const strParts = str.split('.')

  const [ref, type, property] = str.split('.')

  let result

  // same entity _id
  if (strParts.length === 1 && str === '_id') {
    result = [{ _id: entityId }]

  // same entity property
  } else if (strParts.length === 1 && str !== '_id') {
    result = await db.collection('property').find({
      entity: entityId,
      type: str,
      string: { $exists: true },
      deleted: { $exists: false }
    }, {
      sort: { _id: 1 },
      projection: { _id: false, entity: false, type: false }
    }).toArray()

  // childs _id
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] === '_id') {
    const config = {
      'private._parent.reference': entityId,
    }

    if (strParts[1] !== '*') {
      _.set(config, ['private._type.string'], strParts[1])
    }

    result = await db.collection('entity').find(config, {
      projection: { _id: true }
    }).toArray()

  // childs property
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] !== '_id') {
    const config = [
      {
        $match : { 'private._parent.reference': entityId }
      }, {
        $project: { _id: true }
      }, {
        $lookup: {
          from: 'property',
          let: { entityId: '$_id' },
          pipeline: [
            {
              $match: {
                type: strParts[2],
                deleted: { $exists: false },
                $expr: { $eq: [ '$entity',  '$$entityId' ] }
              }
            }, {
              $project: { _id: false, entity: false, type: false, created: false }
            }
          ],
          as: 'properties'
        }
      }, {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              { properties: '$properties' },
              '$$ROOT'
            ]
          }
        }
      }, {
        $project: { _id: false, propertiesq: false }
      }
    ]

    if (strParts[1] !== '*') {
      _.set(config, [0, '$match', 'private._type.string'], strParts[1])
    }

    const p = await db.collection('entity').aggregate(config).toArray()

    result = p.map(x => x.properties).flat()

  }

  return result
}

const formulaValue = (values) => {
  if (!values) { return [] }

  return values.map(x => x.decimal || x.integer || x.datetime || x.date || x.string || x._id)
}
<commit_msg>Cleanup code<commit_after>'use strict'

const _ = require('lodash')
const _h = require('../_helpers')

exports.handler = async (event, context) => {
  if (event.source === 'aws.events') { return _h.json({ message: 'OK' }) }

  if (!event.Records && event.Records.length === 0) { return }

  for (var i = 0; i < event.Records.length; i++) {
    const data = JSON.parse(event.Records[i].body)
    const entityId = _h.strToId(data.entity)
    const db = await _h.db(data.account)

    const entity = await db.collection('entity').findOne({ _id: entityId }, { projection: { _id: false, aggregated: true, 'private.name': true } })

    if (entity && entity.aggregated && data.dt && entity.aggregated >= new Date(data.dt)) {
      console.log('Entity', entityId, '@', data.account, 'already aggregated at', entity.aggregated)
      continue
    }

    const properties = await db.collection('property').find({ entity: entityId, deleted: { $exists: false } }).toArray()

    if (properties.find(x => x.type === '_deleted')) {
      const deleteResponse = await db.collection('entity').deleteOne({ _id: entityId })
      console.log('Entity', entityId, '@', data.account, 'deleted')
      continue
    }

    let newEntity = {
      aggregated: new Date()
    }

    for (var n = 0; n < properties.length; n++) {
      const prop = properties[n]
      let cleanProp = _.omit(prop, ['entity', 'type', 'created', 'search', 'public'])

      if (prop.reference && ['_viewer', '_expander', '_editor', '_owner'].includes(prop.type)) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push(prop.reference)
      }

      if (prop.type === '_public' && prop.boolean === true) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push('public')
      }

      if (!_.has(newEntity, ['private', prop.type])) {
        _.set(newEntity, ['private', prop.type], [])
      }

      if (prop.date) {
        const d = new Date(prop.date)
        cleanProp = { ...cleanProp, string: d.toISOString().substring(0, 9) }
      }

      if (prop.reference) {
        const referenceEntities = await db.collection('entity').findOne({ _id: prop.reference }, { projection: { 'private.name': true }})

        if (_.has(referenceEntities, 'private.name')) {
          cleanProp = referenceEntities.private.name.map(x => {
            return { ...cleanProp, ...x }
          })
        } else {
          cleanProp = { ...cleanProp, string: prop.reference.toString() }
        }
      }

      if (prop.formula) {
        const formulaResult = await formula(prop.formula, entityId, db)
        cleanProp = {...cleanProp, ...formulaResult}
      }

      if (!Array.isArray(cleanProp)) {
        cleanProp = [cleanProp]
      }
      newEntity.private[prop.type] = [...newEntity.private[prop.type], ...cleanProp]

      if (prop.public) {
        if (!_.has(newEntity, ['public', prop.type])) {
          _.set(newEntity, ['public', prop.type], [])
        }

        newEntity.public[prop.type] = [...newEntity.public[prop.type], ...cleanProp]
      }
    }

    const replaceResponse = await db.collection('entity').replaceOne({ _id: entityId }, newEntity)

    const name = _.get(entity, 'private.name', []).map(x => x.string || '')
    const newName = _.get(newEntity, 'private.name', []).map(x => x.string || '')

    if (!_.isEqual(_.sortBy(name), _.sortBy(newName))) {
      const referrers = await db.collection('property').aggregate([
        { $match: { reference: entityId, deleted: { $exists: false } } },
        { $group: { _id: '$entity' } }
      ]).toArray()

      const dt = data.dt ? new Date(data.dt) : newEntity.aggregated

      for (var i = 0; i < referrers.length; i++) {
        await _h.addEntityAggregateSqs(context, data.account, referrers[i]._id.toString(), dt)
      }

      console.log('Entity', entityId, '@', data.account, 'updated and added', referrers.length, 'entities to SQS')
    } else {
      console.log('Entity', entityId, '@', data.account, 'updated')
    }
  }
}

const formula = async (str, entityId, db) => {
  let func = formulaFunction(str)
  let data = formulaContent(str)

  if (!['CONCAT', 'COUNT', 'SUM', 'SUBTRACT', 'AVERAGE', 'MIN', 'MAX'].includes(func)) {
    return { string: str }
  }

  const dataArray = data.split(',')
  let valueArray = []

  for (let i = 0; i < dataArray.length; i++) {
    const value = await formulaField(dataArray[i], entityId, db)

    if (value) {
      valueArray = [...valueArray, ...value]
    }
  }

  valueArray = formulaValue(valueArray)

  switch (func) {
    case 'CONCAT':
      return { string: valueArray.join('') }
      break
    case 'COUNT':
      return { integer: valueArray.length }
      break
    case 'SUM':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) }
      break
    case 'SUBTRACT':
      return { decimal: valueArray.reduce((a, b) => a - b, 0) + (valueArray[0] * 2) }
      break
    case 'AVERAGE':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) / valueArray.length }
      break
    case 'MIN':
      return { decimal: Math.min(...valueArray) }
      break
    case 'MAX':
      return { decimal: Math.max(...valueArray) }
      break
  }
}

const formulaFunction = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return null
  } else {
    return str.substring(0, str.indexOf('(')).toUpperCase()
  }
}

const formulaContent = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return str
  } else {
    return str.substring(str.indexOf('(') + 1, str.lastIndexOf(')'))
  }
}

const formulaField = async (str, entityId, db) => {
  str = str.trim()

  if ((str.startsWith("'") || str.startsWith('"')) && (str.endsWith("'") || str.endsWith('"'))) {
    return [{
      string: str.substring(1, str.length - 1)
    }]
  }

  if (parseFloat(str).toString() === str) {
    return [{
      decimal: parseFloat(str)
    }]
  }

  const strParts = str.split('.')

  const [ref, type, property] = str.split('.')

  let result

  // same entity _id
  if (strParts.length === 1 && str === '_id') {
    result = [{ _id: entityId }]

  // same entity property
  } else if (strParts.length === 1 && str !== '_id') {
    result = await db.collection('property').find({
      entity: entityId,
      type: str,
      string: { $exists: true },
      deleted: { $exists: false }
    }, {
      sort: { _id: 1 },
      projection: { _id: false, entity: false, type: false }
    }).toArray()

  // childs _id
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] === '_id') {
    const config = {
      'private._parent.reference': entityId,
    }

    if (strParts[1] !== '*') {
      _.set(config, ['private._type.string'], strParts[1])
    }

    result = await db.collection('entity').find(config, {
      projection: { _id: true }
    }).toArray()

  // childs property
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] !== '_id') {
    const config = [
      {
        $match : { 'private._parent.reference': entityId }
      }, {
        $project: { _id: true }
      }, {
        $lookup: {
          from: 'property',
          let: { entityId: '$_id' },
          pipeline: [
            {
              $match: {
                type: strParts[2],
                deleted: { $exists: false },
                $expr: { $eq: [ '$entity',  '$$entityId' ] }
              }
            }, {
              $project: { _id: false, entity: false, type: false, created: false }
            }
          ],
          as: 'properties'
        }
      }, {
        $project: { _id: false }
      }
    ]

    if (strParts[1] !== '*') {
      _.set(config, [0, '$match', 'private._type.string'], strParts[1])
    }

    const p = await db.collection('entity').aggregate(config).toArray()

    result = p.map(x => x.properties).flat()

  }

  return result
}

const formulaValue = (values) => {
  if (!values) { return [] }

  return values.map(x => x.decimal || x.integer || x.datetime || x.date || x.string || x._id)
}
<|endoftext|>


<commit_before>// Webflow code to submit form
const conversion = (e, n) => {

	let i = null

	// n stores names already with values (good for checkbox/radio)
	n = n || {}

	const nodelist = e[0].querySelectorAll('input:not([type="submit"])')
	console.log(nodelist)

	nodelist.forEach((item, index) => {
		// If we already have an error, exit early
		if (i) {
			return
		}
		let s = item.type,
			u = item.getAttribute("data-name") || item.getAttribute("name") || "Field " + (index + 1),
			l = item.value

		// Checkbox/radio uses checked attribute and should be handled seperately
		if ("checkbox" === s ||"radio" === s) {
			// If we already have a value for radio/checkbox of this name, exit early
			if (null === n[u] || "string" == typeof n[u]) return
			// get value
			const elem = e[0].querySelector('input[name="' + item.getAttribute("name") + '"]:checked')
			l = elem ? elem.value : null
		}

		if ("string" == typeof l) {
			(l = l.trim())
		}
		n[u] = l
		i = verification(item, s, u, l)

	})

	return i

}

const verification = (item, e, n, i) => {
	let r = null, k = /e(-)?mail/i, _ = /^\S+@\S+$/
	if ("password" === e) {
		return "Passwords cannot be submitted."
	}
	if (!item.getAttribute("required")) {
		return r
	}
	if (!i) {
		return "Please fill out the required field: " + n
	}
	if ((k.test(n) || k.test(item.getAttribute("type"))) && _.test(i)) {
		return "Please enter a valid email address for: " + n
	}
	return null
}

const createForm = () => {
	let formData = {
		name: page() === 'Event' ? 'Event Registration' : 'Custom Charge',
		source: window.location.href,
		test: false,
		fields: {},
		dolphin: false
	}
	let error = conversion(page() === 'Event' ? getElementById(regForm) : getElementById(customForm), formData.fields)
	if (error) {
		throw error
	}
	console.log(formData)
	return formData
}
<commit_msg>015 - webflow form<commit_after>// Webflow code to submit form
const conversion = (e, n) => {

	let i = null

	// n stores names already with values (good for checkbox/radio)
	n = n || {}

	const nodelist = e.querySelectorAll('input:not([type="submit"])')
	console.log(nodelist)

	nodelist.forEach((item, index) => {
		// If we already have an error, exit early
		if (i) {
			return
		}
		let s = item.type,
			u = item.getAttribute("data-name") || item.getAttribute("name") || "Field " + (index + 1),
			l = item.value

		// Checkbox/radio uses checked attribute and should be handled seperately
		if ("checkbox" === s ||"radio" === s) {
			// If we already have a value for radio/checkbox of this name, exit early
			if (null === n[u] || "string" == typeof n[u]) return
			// get value
			const elem = e[0].querySelector('input[name="' + item.getAttribute("name") + '"]:checked')
			l = elem ? elem.value : null
		}

		if ("string" == typeof l) {
			(l = l.trim())
		}
		n[u] = l
		i = verification(item, s, u, l)

	})

	return i

}

const verification = (item, e, n, i) => {
	let r = null, k = /e(-)?mail/i, _ = /^\S+@\S+$/
	if ("password" === e) {
		return "Passwords cannot be submitted."
	}
	if (!item.getAttribute("required")) {
		return r
	}
	if (!i) {
		return "Please fill out the required field: " + n
	}
	if ((k.test(n) || k.test(item.getAttribute("type"))) && _.test(i)) {
		return "Please enter a valid email address for: " + n
	}
	return null
}

const createForm = () => {
	let formData = {
		name: page() === 'Event' ? 'Event Registration' : 'Custom Charge',
		source: window.location.href,
		test: false,
		fields: {},
		dolphin: false
	}
	let error = conversion(page() === 'Event' ? regForm : customForm, formData.fields)
	if (error) {
		throw error
	}
	console.log(formData)
	return formData
}
<|endoftext|>


<commit_before>/*
 * Admin only. Do not include in front pages.
 */

$(function() {

	$('#import-form input.upload').on("click", function(){
		// See http://stackoverflow.com/questions/166221/how-can-i-upload-files-asynchronously-with-jquery#answer-8758614
	    var formData = new FormData($('#import-form')[0]);
	    $.ajax({
	        url: '/admin-data-import-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
				//var count = response.imported;  ???
	        	var count = response.imported
	        	$.fn.pisuccess( count + " idioms imported.");
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Import failed : " + xhr.responseText);
	        },
	        data: formData,
	        cache: false,
	        contentType: false,
	        processData: false
	    });
	});

	$('#refresh-toggles').on("click", function(){
	    $.ajax({
	        url: '/admin-refresh-toggles-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Toggles refreshed" );
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Refresh toggles failed : " + xhr.responseText);
	        },
	    });
	});

	$('.toggles-list button').on("click", function(){
		var toggleName = $(this).html();
		// ?? not up-to-date yet
		var oldValue = $(this).hasClass("active");
		var	newValue = !oldValue;

	    $.ajax({
	        url: '/admin-set-toggle-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Set toggle " + toggleName + " to " + newValue );
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Set toggle " + toggleName + " to " + newValue + " failed : " + xhr.responseText);
	        },
	        data: {
	        	toggle: toggleName,
	        	value: newValue
	        },
	    });
	});

	$('#relation-form input.create-relation').on("click", function(){
		var idA = $("#relation-form input.idiomA").val();
		var idB = $("#relation-form input.idiomB").val();
	    $.ajax({
	        url: '/admin-create-relation-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Created relation between idioms [" + idA + ", " + idB + "]" );
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Relation between idioms [" + idA + ", " + idB + "] failed : " + xhr.responseText );
	        },
	        data: {
	        	idiomAId: idA,
	        	idiomBId: idB
	        },
	    });
	});

	$('#reindex-form input.upload').on("click", function(){
		// See http://stackoverflow.com/questions/166221/how-can-i-upload-files-asynchronously-with-jquery#answer-8758614
	    $.ajax({
	        url: '/admin-reindex-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
				//var count = response.imported;  ???
	        	var count = response.indexed
	        	$.fn.pisuccess( count + " idioms reindexed.");
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Reindex failed : " + xhr.responseText);
	        },
	        cache: false,
	        contentType: false,
	        processData: false
	    });
	});

	$('#message-for-user-form .btn.send-message-for-user').on("click", function(){
	    $.ajax({
	        url: '/admin-send-message-for-user',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Message has been sent." );
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Message sending failed : " + xhr.responseText );
	        },
	        data: $("#message-for-user-form").serialize(),
	    });
	});
});
<commit_msg>MessageForUser admin UI: clear text on success.<commit_after>/*
 * Admin only. Do not include in front pages.
 */

$(function() {

	$('#import-form input.upload').on("click", function(){
		// See http://stackoverflow.com/questions/166221/how-can-i-upload-files-asynchronously-with-jquery#answer-8758614
	    var formData = new FormData($('#import-form')[0]);
	    $.ajax({
	        url: '/admin-data-import-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
				//var count = response.imported;  ???
	        	var count = response.imported
	        	$.fn.pisuccess( count + " idioms imported.");
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Import failed : " + xhr.responseText);
	        },
	        data: formData,
	        cache: false,
	        contentType: false,
	        processData: false
	    });
	});

	$('#refresh-toggles').on("click", function(){
	    $.ajax({
	        url: '/admin-refresh-toggles-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Toggles refreshed" );
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Refresh toggles failed : " + xhr.responseText);
	        },
	    });
	});

	$('.toggles-list button').on("click", function(){
		var toggleName = $(this).html();
		// ?? not up-to-date yet
		var oldValue = $(this).hasClass("active");
		var	newValue = !oldValue;

	    $.ajax({
	        url: '/admin-set-toggle-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Set toggle " + toggleName + " to " + newValue );
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Set toggle " + toggleName + " to " + newValue + " failed : " + xhr.responseText);
	        },
	        data: {
	        	toggle: toggleName,
	        	value: newValue
	        },
	    });
	});

	$('#relation-form input.create-relation').on("click", function(){
		var idA = $("#relation-form input.idiomA").val();
		var idB = $("#relation-form input.idiomB").val();
	    $.ajax({
	        url: '/admin-create-relation-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Created relation between idioms [" + idA + ", " + idB + "]" );
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Relation between idioms [" + idA + ", " + idB + "] failed : " + xhr.responseText );
	        },
	        data: {
	        	idiomAId: idA,
	        	idiomBId: idB
	        },
	    });
	});

	$('#reindex-form input.upload').on("click", function(){
		// See http://stackoverflow.com/questions/166221/how-can-i-upload-files-asynchronously-with-jquery#answer-8758614
	    $.ajax({
	        url: '/admin-reindex-ajax',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
				//var count = response.imported;  ???
	        	var count = response.indexed
	        	$.fn.pisuccess( count + " idioms reindexed.");
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Reindex failed : " + xhr.responseText);
	        },
	        cache: false,
	        contentType: false,
	        processData: false
	    });
	});

	$('#message-for-user-form .btn.send-message-for-user').on("click", function(){
	    $.ajax({
	        url: '/admin-send-message-for-user',
	        type: 'POST',
	        xhr: function() {
	            var myXhr = $.ajaxSettings.xhr();
	            return myXhr;
	        },
	        success: function(response){
	        	$.fn.pisuccess( "Message has been sent." );
	        	$('#message-for-user-form textarea[name=message]').val('');
	        },
	        error: function(xhr, status, e){
	        	$.fn.pierror( "Message sending failed : " + xhr.responseText );
	        },
	        data: $("#message-for-user-form").serialize(),
	    });
	});
});
<|endoftext|>


<commit_before>(function() {
  'use strict';

  var nav = navigator.userAgent.toLowerCase(),
    isIE = nav.indexOf('msie') !== -1,
    ieVersion = isIE ? parseInt(nav.split('msie')[1]) : -1;

  define({
    /**
     * Generate random string suitable for use as a request ID
     */
    randomID: function() {
      return parseInt(Math.random() * 10e10).toString();
    },

    /**
     * Determines if the browser supports cross-domain requests via the normal
     * XMLHttpRequest object, or if we need to use the special IE-only
     * XDomainRequest object (proxied through the iexd iframe).
     *
     * Supported by most non-IE and IE >= 10.
     */
    supportsCORS: function() {
      return !isIE || !(ieVersion < 10);
    },

    /**
     * Determines if the browser supports postMessage to popups with a
     * different origin.
     *
     * Supported by most non-IE and IE >= 11
     */
    supportsPopupPostMessage: function() {
      return !isIE || !(ieVersion < 11);
    },
  });
})();
<commit_msg>explorer/util: don't use postMessage popup on IE11<commit_after>(function() {
  'use strict';

  var nav = navigator.userAgent.toLowerCase(),
    isIE = false,
    ieVersion = -1;

  /*
   * IE 11 removes 'msie' from the UA string so that apps which sniff it to
   * determine capabilities won't serve it crippled code.
   *
   * This might have been a good idea if we didn't still need to workaround its
   * annoying bugs. But we do.
   */
  if (nav.indexOf('msie') !== -1) { // IE < 11
    isIE = true;
    ieVersion = parseInt(nav.split('msie')[1]);
  } else if (nav.indexOf('trident/') !== -1) { // IE 11+
    isIE = true;
    ieVersion = parseInt(nav.split('rv:')[1]);
  }

  define({
    /**
     * Generate random string suitable for use as a request ID
     */
    randomID: function() {
      return parseInt(Math.random() * 10e10).toString();
    },

    /**
     * Determines if the browser supports cross-domain requests via the normal
     * XMLHttpRequest object, or if we need to use the special IE-only
     * XDomainRequest object (proxied through the iexd iframe).
     *
     * Supported by most non-IE and IE >= 10.
     */
    supportsCORS: function() {
      return !isIE || !(ieVersion < 10);
    },

    /**
     * Determines if the browser supports postMessage to popups with a
     * different origin.
     *
     * Supported by most non-IE; not supported IE <= 11. Seems to be supported
     * on (currently unreleased version of) IE 12, but needs testing after
     * release.
     */
    supportsPopupPostMessage: function() {
      return !isIE;
    },
  });
})();
<|endoftext|>


<commit_before>var Tween = require('../tween/Tween');

module.exports = {

    pushFromTop: function(){
        return function(transition){
            transition.sortScenes(transition.sceneOut, transition.sceneIn);

            var tweenIn = new Tween(transition.sceneIn, transition.manager.tweenManager)
                .from({
                    x : 0,
                    y : -transition.sceneIn.height
                }).to({
                    x : 0,
                    y : 0
                }).setTime(4000)
                .setExpire()

                .onStart(function(){
                    this.target.visible = true;
                })

                .onEnd(function(){
                    transition.manager.setCurrentScene(transition.sceneIn);
                });

            tweenIn.start();

            var tweenOut = new Tween(transition.sceneOut, transition.manager.tweenManager)
                .from({
                    x : 0,
                    y : 0
                }).to({
                    x : 0,
                    y : transition.sceneOut.height
                }).setTime(4000)
                .setExpire();

            tweenOut.start();
        }
    }

};<commit_msg>added new transition effects<commit_after>var Tween = require('../tween/Tween');

module.exports = {

    pushFromTop: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : 0,
                    y : -sceneIn.height
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

            var tweenOut = new Tween(sceneOut, transition.tweenManager)
                .from({
                    x : 0,
                    y : 0
                }).to({
                    x : 0,
                    y : sceneOut.height
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire();

            tweenOut.start();
        };
    },

    pushFromBottom: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : 0,
                    y : sceneIn.height
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

            var tweenOut = new Tween(sceneOut, transition.tweenManager)
                .from({
                    x : 0,
                    y : 0
                }).to({
                    x : 0,
                    y : -sceneOut.height
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire();

            tweenOut.start();
        };
    },

    pushFromLeft: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : -sceneIn.width,
                    y : 0
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

            var tweenOut = new Tween(sceneOut, transition.tweenManager)
                .from({
                    x : 0,
                    y : 0
                }).to({
                    x : sceneOut.width,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire();

            tweenOut.start();
        };
    },

    pushFromRight: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : sceneIn.width,
                    y : 0
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

            var tweenOut = new Tween(sceneOut, transition.tweenManager)
                .from({
                    x : 0,
                    y : 0
                }).to({
                    x : -sceneOut.width,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire();

            tweenOut.start();
        };
    },

    overlapFromTop: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : 0,
                    y : -sceneIn.height
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

        };
    },

    overlapFromBottom: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : 0,
                    y : sceneIn.height
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

        };
    },

    overlapFromRight: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : -sceneIn.width,
                    y : 0
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

        };
    },

    overlapFromLeft: function(){
        return function(transition, sceneOut, sceneIn){
            transition.sortScenes(sceneOut, sceneIn);

            var tweenIn = new Tween(sceneIn, transition.tweenManager)
                .from({
                    x : sceneIn.width,
                    y : 0
                }).to({
                    x : 0,
                    y : 0
                }).setTime(transition.time)
                .setDelay(transition.delay)
                .setEasing(transition.easing)
                .setExpire()

                .onStart(transition._onTransitionStart.bind(transition))
                .onEnd(transition._onTransitionEnd.bind(transition));

            tweenIn.start();

        };
    }

};<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.INTERFACE({
  package: 'foam.dao',
  name: 'Journal',

  methods: [
    {
      name: 'put',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'nu', type: 'foam.core.FObject' }
      ]
    },
    {
      name: 'put_',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'old', type: 'foam.core.FObject' },
        { name: 'nu', type: 'foam.core.FObject' }
      ]
    },
    {
      name: 'remove',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'obj', type: 'foam.core.FObject' }
      ]
    },
    {
      name: 'replay',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'dao', type: 'foam.dao.DAO' }
      ]
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'AbstractJournal',
  abstract: true,

  implements: [
    'foam.dao.Journal'
  ],

  methods: [
    {
      name: 'put',
      code: function (x, nu) {
        this.put_(x, null, nu);
      },
      javaCode: `
        this.put_(x, null, nu);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyJournal',
  extends: 'foam.dao.AbstractJournal',

  documentation: 'Proxy journal class',

  properties: [
    {
      class: 'Proxy',
      of: 'foam.dao.Journal',
      name: 'delegate',
      forwards: [ 'put_', 'remove', 'replay' ]
    }
  ]
});

if ( foam.isServer ) {
  foam.CLASS({
    package: 'foam.dao',
    name: 'NodeFileJournal',
    extends: 'foam.dao.AbstractJournal',

    properties: [
      {
        class: 'Class',
        name: 'of',
        value: 'foam.core.FObject'
      },
      {
        name: 'fd',
        required: true
      },
      {
        name: 'offset',
        factory: function() {
          var stat = this.fs.fstatSync(this.fd);
          return stat.size;
        }
      },
      {
        name: 'fs',
        factory: function() { return require('fs'); }
      },
      {
        name: 'writePromise',
        value: Promise.resolve()
      }
    ],

    methods: [
      function put_(x, old, nu) {
        return this.write_(Buffer.from(
            "put(foam.json.parse(" + foam.json.Storage.stringify(nu, this.of) +
              "));\n"));
      },

      function remove(x, obj) {
        return this.write_(Buffer.from(
            "remove(foam.json.parse(" +
              foam.json.Storage.stringify(obj, this.of) +
              "));\n"));
      },

      function write_(data) {
        var self = this;
        var offset = self.offset;
        self.offset += data.length;
        return self.writePromise = self.writePromise.then(function() {
          return new Promise(function(resolve, reject) {
            self.fs.write(
                self.fd, data, 0, data.length, offset,
                function(err, written, buffer) {
                  if ( err ) reject(err);
                  if ( written != data.length )
                    reject(new Error('foam.dao.NodeFileJournal: Incomplete write'));
                  resolve();
                });
          });
        });
      },

      function replay(x, dao) {
        var self = this;
        return new Promise(function(resolve, reject) {
          self.fs.readFile(self.fd, 'utf8', function(err, data_) {
            if ( err ) {
              reject(err);
              return;
            }

            var context = {
              put: function(o) { return dao.put(o); },
              remove: function(o) { return dao.remove(o); },
              foam: {
                json: {
                  parse: function(obj) {
                    return foam.json.parse(obj, self.of, dao.__context__);
                  }
                }
              }
            };

            with(context) eval(data_);

            resolve(dao);
          });
        });
      }
    ]
  });
}
<commit_msg>Formatting.<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.INTERFACE({
  package: 'foam.dao',
  name: 'Journal',

  methods: [
    {
      name: 'put',
      args: [
        { name: 'x',  type: 'Context' },
        { name: 'nu', type: 'foam.core.FObject' }
      ]
    },
    {
      name: 'put_',
      args: [
        { name: 'x',   type: 'Context' },
        { name: 'old', type: 'foam.core.FObject' },
        { name: 'nu',  type: 'foam.core.FObject' }
      ]
    },
    {
      name: 'remove',
      args: [
        { name: 'x',   type: 'Context' },
        { name: 'obj', type: 'foam.core.FObject' }
      ]
    },
    {
      name: 'replay',
      args: [
        { name: 'x',   type: 'Context' },
        { name: 'dao', type: 'foam.dao.DAO' }
      ]
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'AbstractJournal',
  abstract: true,

  implements: [
    'foam.dao.Journal'
  ],

  methods: [
    {
      name: 'put',
      code: function (x, nu) {
        this.put_(x, null, nu);
      },
      javaCode: `
        this.put_(x, null, nu);
      `
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyJournal',
  extends: 'foam.dao.AbstractJournal',

  documentation: 'Proxy journal class',

  properties: [
    {
      class: 'Proxy',
      of: 'foam.dao.Journal',
      name: 'delegate',
      forwards: [ 'put_', 'remove', 'replay' ]
    }
  ]
});

if ( foam.isServer ) {
  foam.CLASS({
    package: 'foam.dao',
    name: 'NodeFileJournal',
    extends: 'foam.dao.AbstractJournal',

    properties: [
      {
        class: 'Class',
        name: 'of',
        value: 'foam.core.FObject'
      },
      {
        name: 'fd',
        required: true
      },
      {
        name: 'offset',
        factory: function() {
          var stat = this.fs.fstatSync(this.fd);
          return stat.size;
        }
      },
      {
        name: 'fs',
        factory: function() { return require('fs'); }
      },
      {
        name: 'writePromise',
        value: Promise.resolve()
      }
    ],

    methods: [
      function put_(x, old, nu) {
        return this.write_(Buffer.from(
            "put(foam.json.parse(" + foam.json.Storage.stringify(nu, this.of) +
              "));\n"));
      },

      function remove(x, obj) {
        return this.write_(Buffer.from(
            "remove(foam.json.parse(" +
              foam.json.Storage.stringify(obj, this.of) +
              "));\n"));
      },

      function write_(data) {
        var self = this;
        var offset = self.offset;
        self.offset += data.length;
        return self.writePromise = self.writePromise.then(function() {
          return new Promise(function(resolve, reject) {
            self.fs.write(
                self.fd, data, 0, data.length, offset,
                function(err, written, buffer) {
                  if ( err ) reject(err);
                  if ( written != data.length )
                    reject(new Error('foam.dao.NodeFileJournal: Incomplete write'));
                  resolve();
                });
          });
        });
      },

      function replay(x, dao) {
        var self = this;
        return new Promise(function(resolve, reject) {
          self.fs.readFile(self.fd, 'utf8', function(err, data_) {
            if ( err ) {
              reject(err);
              return;
            }

            var context = {
              put: function(o) { return dao.put(o); },
              remove: function(o) { return dao.remove(o); },
              foam: {
                json: {
                  parse: function(obj) {
                    return foam.json.parse(obj, self.of, dao.__context__);
                  }
                }
              }
            };

            with(context) eval(data_);

            resolve(dao);
          });
        });
      }
    ]
  });
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'CheckBox',
  extends: 'foam.u2.tag.Input',

  documentation: 'Checkbox View.',

  properties: [
    {
      class: 'Boolean',
      name: 'data'
    },
    {
      class: 'Boolean',
      name: 'showLabel',
      factory: function() { return !! this.label || this.labelFormatter },
    },
    {
      class: 'String',
      name: 'label'
    },
    {
      name: 'labelFormatter'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.setAttribute('type', 'checkbox');

      var self = this;

      if ( this.showLabel ) {
        this.start('label')
          .addClass(this.myClass('label'))
          .addClass(this.myClass('noselect'))
          .callIfElse(this.labelFormatter,
                      this.labelFormatter,
                      function() { this.add(self.label$); })
          .on('click', function() {
            if ( self.getAttribute('disabled') ) return;
            this.data = ! this.data;
          }.bind(this))
        .end();
      }
    },

    function updateMode_(mode) {
      var disabled = mode === foam.u2.DisplayMode.RO ||
                     mode === foam.u2.DisplayMode.DISABLED;
      this.setAttribute('disabled', disabled);
    },

    function link() {
      this.data$.linkTo(this.attrSlot('checked'));
    }
  ],

  css: `
    ^ {
      margin: 0;
      padding: 8px;
    }

    ^label {
      color: #444;
      flex-grow: 1;
      margin-left: 12px;
      overflow: hidden;
      white-space: nowrap;
      display: inline;
    }

    ^noselect {
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
  `
});
<commit_msg>Use property's label for checkbox label<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'CheckBox',
  extends: 'foam.u2.tag.Input',

  documentation: 'Checkbox View.',

  properties: [
    {
      class: 'Boolean',
      name: 'data'
    },
    {
      class: 'Boolean',
      name: 'showLabel',
      factory: function() { return !! this.label || this.labelFormatter },
    },
    {
      class: 'String',
      name: 'label'
    },
    {
      name: 'labelFormatter'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.setAttribute('type', 'checkbox');

      var self = this;

      if ( this.showLabel ) {
        this.start('label')
          .addClass(this.myClass('label'))
          .addClass(this.myClass('noselect'))
          .callIfElse(this.labelFormatter,
                      this.labelFormatter,
                      function() { this.add(self.label$); })
          .on('click', function() {
            if ( self.getAttribute('disabled') ) return;
            this.data = ! this.data;
          }.bind(this))
        .end();
      }
    },

    function updateMode_(mode) {
      var disabled = mode === foam.u2.DisplayMode.RO ||
                     mode === foam.u2.DisplayMode.DISABLED;
      this.setAttribute('disabled', disabled);
    },

    function link() {
      this.data$.linkTo(this.attrSlot('checked'));
    },

    function fromProperty(property) {
      this.SUPER(property);
      this.label = property.label;
    }
  ],

  css: `
    ^ {
      margin: 0;
      padding: 8px;
    }

    ^label {
      color: #444;
      flex-grow: 1;
      margin-left: 12px;
      overflow: hidden;
      white-space: nowrap;
      display: inline;
    }

    ^noselect {
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
  `
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO: Add datalist support.

foam.CLASS({
  package: 'foam.u2',
  name: 'DateView',
  extends: 'foam.u2.tag.Input',

  documentation: 'View for editing Date values.',

  axioms: [
    foam.u2.CSS.create({
      code: '^:read-only { border: none; background: rgba(0,0,0,0); }'
    })
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.setAttribute('type', 'date');
    },

    function link() {
      this.data$.relateTo(
          this.attrSlot(null, this.onKey ? 'input' : null),
          function(date) {
            return date ? date.toISOString().substring(0,10) : date;
          },
          function(value) {
            return new Date(value);
          }
      );
    }
  ]
});
<commit_msg>Set placeholder for DateView on Safari.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO: Add datalist support.

foam.CLASS({
  package: 'foam.u2',
  name: 'DateView',
  extends: 'foam.u2.tag.Input',

  documentation: 'View for editing Date values.',

  css: '^:read-only { border: none; background: rgba(0,0,0,0); }',

  properties: [
    [ 'placeholder', 'yyyy-mm-dd' ]
  ],

  methods: [
    function initE() {
      this.SUPER();
      this.setAttribute('type', 'date');
    },

    function link() {
      this.data$.relateTo(
          this.attrSlot(null, this.onKey ? 'input' : null),
          function(date) {
            return date ? date.toISOString().substring(0,10) : date;
          },
          function(value) {
            return new Date(value);
          }
      );
    }
  ]
});
<|endoftext|>


<commit_before>var _d = {
    bb_active: '',
    tx_active: '',
    hs_active: '',
    windows: {},
};

var _w = {
    edit: { text: 'Edit', submit: 'submitEdit', cancel: 'closeEdit' },
    reply: { text: 'Reply', submit: 'submitReply', cancel: 'closeReply' },
};

var _copy = {
    range: undefined,
    buffer: undefined,
    id: undefined,
    user: undefined,
};

function openWindow(num, wnd_id, text, btn_id, next) {
    var w = _w[text];

    $.ajax({
        type: 'POST',
        dataType: 'text',
        global: false,
        url: '/window',
        data: 'id=' + wnd_id +
            '&submit=' + w.submit + '&cancel=' + w.cancel + '&text=' + w.text,
        success: function(data) {
            $(data).insertAfter(num);

            initalizeExpandingAreas();

            $('#te-' + wnd_id).keydown(keyHandler(wnd_id, false));

            next();
        }
    });
}

function showPreview(wnd_id) {
    var txt = $('#' + wnd_id + ' textarea').val();
    var sel = $('#rnd-' + wnd_id + ' label.active input').val();
    $.ajax({
        type: 'POST',
        dataType: 'text',
        global: false,
        url: '/preview-post',
        data: 'r=' + sel + '&t=' + encodeURIComponent(txt),
        success: function(data) {
            $('#prv-' + wnd_id).html(data);
        },
        error: function() {
            Dialog.alert({
                size: BootstrapDialog.SIZE_LARGE,
                type: BootstrapDialog.TYPE_DANGER,
                title: 'Warning',
                message: 'Could not preview (server error).',
            });
        }
    });
}

function close(data, wnd_id) {
    var d = _d.windows[wnd_id];
    $('#' + wnd_id).remove();
    $(d.num).data(data, false);
    $(d.btn_id).css('color', $(d.num).data('old-color'));
}

function reply(btn, num, pid) {
    var btn_id = '#' + $(btn).attr('id');
    var wnd_id = 'r' + pid;

    if($(num).data('edit-open')) {
        closeEdit('e' + pid);
    }

    if($(num).data('reply-open')) {
        closeReply(wnd_id);

    } else {
        openWindow(num, wnd_id, 'reply', btn_id, function() {
            $('#rnd-' + wnd_id + ' label').removeClass('active');
            $('#rnd-' + wnd_id + '-' + _g.Transform).addClass('active');
            _d.windows[wnd_id] = {
                btn_id: btn_id,
                num: num,
                pid: pid,
            }
            delaySetFocus(wnd_id);
        });
        $(num).data('reply-open', true).
            data('old-color', $(btn).css('color'));
        $(btn).css('color', colFromId(_g.ColourId));
    }
}

function closeReply(wnd_id) {
    close('reply-open', wnd_id);
}

function submitReply(wnd_id) {

    var sel = $('#rnd-' + wnd_id + ' label.active input').val();
    var txt = encodeURIComponent($('#' + wnd_id + ' textarea').val());
    var tags = encodeTagString($('#tags-' + wnd_id).val());
    var pid = _d.windows[wnd_id].pid;
    $.ajax({
        type: 'POST',
        dataType: 'json',
        global: false,
        url: '/createpost',
        data: 'r=' + sel + '&t=' + txt + '&tg=' + tags +
            '&p=' + pid + '&th=' + _g.ThreadId,
        success: function(data) {
            reloadPageContent();
        },
        error: function() {
            Dialog.alert({
                size: BootstrapDialog.SIZE_LARGE,
                type: BootstrapDialog.TYPE_DANGER,
                title: 'Warning',
                message: 'Could not post (server error).',
            });
        }
    });
}

function edit(btn, num, pid) {
    var btn_id = '#' + $(btn).attr('id');
    var wnd_id = 'e' + pid;

    if($(num).data('reply-open')) {
        closeReply('r' + pid);
    }

    if($(num).data('edit-open')) {
        closeEdit(wnd_id);

    } else {
        openWindow(num, wnd_id, 'edit', btn_id, function() {
            populateEditData(pid);
            $('#te-' + wnd_id).focus();
            _d.windows[wnd_id] = {
                btn_id: btn_id,
                num: num,
                pid: pid,
            }
            delaySetFocus(wnd_id);
        });

        $(num).data('edit-open', true).
            data('old-color', $(btn).css('color'));
        $(btn).css('color', colFromId(_g.ColourId));
    }
}

function closeEdit(wnd_id) {
    close('edit-open', wnd_id);
}

function populateEditData(pid) {
    var wnd_id = 'e' + pid;
    $.ajax({
        type: 'POST',
        dataType: 'json',
        global: false,
        url: '/get-post',
        data: 'p=' + pid,
        success: function(data) {
            $('#te-' + wnd_id).html(data.i);
            $('#prv-' + wnd_id).html(data.o);
            selectTags($('select#tags-' + wnd_id), decodeTagString(data.tg));
            $('#te-' + wnd_id).trigger('input');
            $('#rnd-' + wnd_id + ' label').removeClass('active');
            var t = data.t || _g.Transform;
            $('#rnd-' + wnd_id + '-' + t).addClass('active');
        }
    });
}

function submitEdit(wnd_id) {

    var sel = $('#rnd-' + wnd_id + ' label.active input').val();
    var txt = encodeURIComponent($('#' + wnd_id + ' textarea').val());
    var tags = encodeTagString($('#tags-' + wnd_id).val());
    var pid = _d.windows[wnd_id].pid;

    $.ajax({
        type: 'POST',
        dataType: 'json',
        global: false,
        url: '/update-post',
        data: 'p=' + pid + '&r=' + sel + '&t=' + txt + '&tg=' + tags,
        success: function(data) {
            var d = _d.windows[wnd_id];

            closeEdit(wnd_id);
            $('#c-' + pid).html(data.o);
            $('#tg-' + pid).html(tagsHtml(decodeTagString(data.tg)));
        },
        error: function() {
            Dialog.alert({
                size: BootstrapDialog.SIZE_LARGE,
                type: BootstrapDialog.TYPE_DANGER,
                title: 'Warning',
                message: 'Could not edit (server error).',
            });
        }
    });
}

function quote(pid, on) {
    var num = '#q-' + pid;
    if(on) {
        if(!$(num).data('quote-open')) {
            $(num).data('quote-open', true).
                data('old-color', $(num).css('color'));
            $(num).css('color', colFromId(_g.ColourId));
        }
    } else {
        if($(num).data('quote-open')) {
            $(num).data('quote-open', false);
            $(num).css('color', $(num).data('old-color'));
        }
    }
}

function checkSelection() {

    var sel = document.getSelection();
    if(sel && sel.rangeCount > 0) {

        var rng = sel.getRangeAt(0);
        if( rng && !rng.collapsed) {
            if( (!_copy.range ||
                    (rng.compareBoundaryPoints(Range.START_TO_START, _copy.range) != 0) ||
                    (rng.compareBoundaryPoints(Range.END_TO_END, _copy.range) != 0)) ) {

                var node = rng.commonAncestorContainer;
                var ele = node;
                if(Node.ELEMENT_NODE !== node.nodeType) {
                    ele = node.parentElement;
                }
                while(node && !node.id) {
                    node = node.parentElement;
                }
                if(node && node.id.match(/c-[0-9]+/)) {
                    var got;
                    var el = document.createElement(ele.tagName);
                    el.appendChild(rng.cloneContents());

                    for(var i = 0; i < el.children.length; i++) {
                        var e = el.children[i];
                        for(var j = 0; j < e.attributes.length; j++) {
                            e.removeAttribute(e.attributes[j].name);
                        }
                    }
                    switch(ele.tagName) {
                        case 'DIV': case 'P': case 'SPAN':
                        case 'div': case 'p': case 'span': {
                            got = el.innerHTML;
                            break;
                        }
                        default: {
                            got = el.outerHTML;
                            break;
                        }
                    }
                    _copy.buffer = got;
                    _copy.id = node.id.replace('c-', '');
                    _copy.user = node.getAttribute('data-author');
                    quote(_copy.id, true);
                }
                _copy.range = rng;
            }
        } else {
            if(_copy.id) {
                quote(_copy.id, false);
            }
            _copy.range = undefined;
            _copy.buffer = undefined;
            _copy.id = undefined;
            _copy.user = undefined;
        }
    }
}

function thumbsup(btn, pid) {
    var cnt = '#tc-' + pid;
    if($(btn).data('thumbed')) {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/thumb',
            data: 'p=' + pid,
            success: function(data) {
                $(btn).data('thumbed', false).
                    css('color', $(btn).data('old-color')).
                    attr('class', 'fa fa-fw fa-thumbs-o-up');
                var c = $(cnt).text();
                if(c > 0) { c--; }
                $(cnt).text(c);
            }
        });
    } else {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/thumb',
            data: 'p=' + pid + '&on=true',
            success: function(data) {
                $(btn).data('thumbed', true).
                    data('old-color', $(btn).css('color')).
                    css('color', colFromId(_g.ColourId)).
                    attr('class', 'fa fa-fw fa-thumbs-up');
                var c = $(cnt).text();
                c++;
                $(cnt).text(c);
            }
        });
    }
}

function star(btn, pid) {
    var cnt = '#bc-' + pid;
    if($(btn).data('starred')) {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/star',
            data: 'p=' + pid,
            success: function(data) {
                $(btn).data('starred', false).
                    css('color', $(btn).data('old-color')).
                    attr('class', 'fa fa-fw fa-star-o');
                var c = $(cnt).text();
                if(c > 0) { c--; }
                $(cnt).text(c);
            }
        });
    } else {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/star',
            data: 'p=' + pid + '&on=true',
            success: function(data) {
                $(btn).data('starred', true).
                    data('old-color', $(btn).css('color')).
                    css('color', colFromId(_g.ColourId)).
                    attr('class', 'fa fa-fw fa-star');
                var c = $(cnt).text();
                c++;
                $(cnt).text(c);
            }
        });
    }
}

function initThreadPage(thid, transform, from, to) {
    _g.ThreadId = thid;
    _g.Transform = transform;
    _g.From = from;
    _g.To = to;

    switch(transform) {
        case 'bbcode': {
            _d.bb_active = 'active';
            break;
        }
        case 'textile': {
            _d.tx_active = 'active';
            break;
        }
        default: {
            _d.hs_active = 'active';
            break;
        }
    }

    $('span[data-thumbed="true"], span[data-starred="true"]').each(function () {
        $(this).css('color', colFromId(_g.ColourId));
    });
}

// insert tag array into the select2 object as selected options, so the tags
// field is populated properly...
function selectTags(select, tags) {
    for(var i = 0; i < tags.length; i++) {
        var opt = $('<option></option>').
            attr('selected', true).text(tags[i]).val(tags[i]);
        opt.appendTo(select);
    }
    select.trigger('change');
}

// FIXME?
function delaySetFocus(wnd_id) {
    setTimeout(function() {
        $('#te-' + wnd_id).focus();
    }, 100);
}

function keyHandler(wnd_id, suppress_open_close) {
    return function(e) {
        if(e.ctrlKey) {
            switch(e.keyCode) {
                case 13: { //'Enter'
                    if(!suppress_open_close) {
                        $('#btn-' + wnd_id + '-submit').click();
                        e.preventDefault();
                    }
                    break;
                }
                case 80: { //'P'
                    $('#btn-' + wnd_id + '-preview').click();
                    e.preventDefault();
                    break;
                }
                case 85: { //'U'
                    $('#btn-' + wnd_id + '-bb').click();
                    e.preventDefault();
                    break;
                }
                case 73: { //'I'
                    $('#btn-' + wnd_id + '-tx').click();
                    e.preventDefault();
                    break;
                }
                case 79: { //'O'
                    $('#btn-' + wnd_id + '-hs').click();
                    e.preventDefault();
                    break;
                }
            }
        }
        if(e.keyCode === 27) {
            if(!suppress_open_close) {
                $('#btn-' + wnd_id + '-cancel').click();
                e.preventDefault();
            }
        }
    }
}

function replyFirst() {
    openWindow('#0', 'r0', 'reply', undefined, function() {
        $('#0').remove();
        $('#rnd-r0 label').removeClass('active');
        $('#rnd-r0-' + _g.Transform).addClass('active');
        _d.windows['r0'] = {
            btn_id: '',
            num: '#0',
            pid: 'X',
        }
        delaySetFocus('r0');
    });
}

// install handler for (hacked) keypress events from select2 elements
$(document).ready(function() {
    $('body').on("s2_keypress", function(_, e) {
        var wnd_id = e.id.replace("select2-tags-", "");
        keyHandler(wnd_id, e.open)(e);
    });

    setInterval(checkSelection, 350);

    document.addEventListener('copy', function(e) {
        checkSelection();

        if(_copy.user && _copy.buffer) {
            var buf = '<q><cite>' + _copy.user + '</cite>' + _copy.buffer + '</q>';
            e.clipboardData.clearData('text/plain');
            e.clipboardData.setData('text/plain', buf);
            e.preventDefault();
        }
        return true;
    });
})
<commit_msg>[http]<commit_after>var _d = {
    bb_active: '',
    tx_active: '',
    hs_active: '',
    windows: {},
};

var _w = {
    edit: { text: 'Edit', submit: 'submitEdit', cancel: 'closeEdit' },
    reply: { text: 'Reply', submit: 'submitReply', cancel: 'closeReply' },
};

var _copy = {
    range: undefined,
    buffer: undefined,
    id: undefined,
    user: undefined,
};

function openWindow(num, wnd_id, text, next) {
    var w = _w[text];

    $.ajax({
        type: 'POST',
        dataType: 'text',
        global: false,
        url: '/window',
        data: 'id=' + wnd_id +
            '&submit=' + w.submit + '&cancel=' + w.cancel + '&text=' + w.text,

        success: function(data) {
            $(data).insertAfter(num);
            initalizeExpandingAreas();
            $('#te-' + wnd_id).keydown(keyHandler(wnd_id, false));
            next();
        }
    });
}

function showPreview(wnd_id) {
    var txt = $('#' + wnd_id + ' textarea').val();
    var sel = $('#rnd-' + wnd_id + ' label.active input').val();
    $.ajax({
        type: 'POST',
        dataType: 'text',
        global: false,
        url: '/preview-post',
        data: 'r=' + sel + '&t=' + encodeURIComponent(txt),

        success: function(data) {
            $('#prv-' + wnd_id).html(data);
        },
        error: function() {
            Dialog.alert({
                size: BootstrapDialog.SIZE_LARGE,
                type: BootstrapDialog.TYPE_DANGER,
                title: 'Warning',
                message: 'Could not preview (server error).',
            });
        }
    });
}

function close(data, wnd_id) {
    var d = _d.windows[wnd_id];
    $('#' + wnd_id).remove();
    $(d.num).data(data, false);
    $(d.btn_id).css('color', $(d.num).data('old-color'));
}

function reply(btn, num, pid) {
    var btn_id = '#' + $(btn).attr('id');
    var wnd_id = 'r' + pid;

    if($(num).data('edit-open')) {
        closeEdit('e' + pid);
    }

    if($(num).data('reply-open')) {
        closeReply(wnd_id);

    } else {
        openWindow(num, wnd_id, 'reply', function() {
            $('#rnd-' + wnd_id + ' label').removeClass('active');
            $('#rnd-' + wnd_id + '-' + _g.Transform).addClass('active');
            _d.windows[wnd_id] = {
                btn_id: btn_id,
                num: num,
                pid: pid,
            }
            delaySetFocus(wnd_id);
        });
        $(num).data('reply-open', true).
            data('old-color', $(btn).css('color'));
        $(btn).css('color', colFromId(_g.ColourId));
    }
}

function closeReply(wnd_id) {
    close('reply-open', wnd_id);
}

function submitReply(wnd_id) {

    var sel = $('#rnd-' + wnd_id + ' label.active input').val();
    var txt = encodeURIComponent($('#' + wnd_id + ' textarea').val());
    var tags = encodeTagString($('#tags-' + wnd_id).val());
    var pid = _d.windows[wnd_id].pid;
    $.ajax({
        type: 'POST',
        dataType: 'json',
        global: false,
        url: '/createpost',
        data: 'r=' + sel + '&t=' + txt + '&tg=' + tags +
            '&p=' + pid + '&th=' + _g.ThreadId,

        success: function(data) {
            reloadPageContent();
        },
        error: function() {
            Dialog.alert({
                size: BootstrapDialog.SIZE_LARGE,
                type: BootstrapDialog.TYPE_DANGER,
                title: 'Warning',
                message: 'Could not post (server error).',
            });
        }
    });
}

function edit(btn, num, pid) {
    var btn_id = '#' + $(btn).attr('id');
    var wnd_id = 'e' + pid;

    if($(num).data('reply-open')) {
        closeReply('r' + pid);
    }

    if($(num).data('edit-open')) {
        closeEdit(wnd_id);

    } else {
        openWindow(num, wnd_id, 'edit', function() {
            populateEditData(pid);
            $('#te-' + wnd_id).focus();
            _d.windows[wnd_id] = {
                btn_id: btn_id,
                num: num,
                pid: pid,
            }
            delaySetFocus(wnd_id);
        });

        $(num).data('edit-open', true).
            data('old-color', $(btn).css('color'));
        $(btn).css('color', colFromId(_g.ColourId));
    }
}

function closeEdit(wnd_id) {
    close('edit-open', wnd_id);
}

function populateEditData(pid) {
    var wnd_id = 'e' + pid;
    $.ajax({
        type: 'POST',
        dataType: 'json',
        global: false,
        url: '/get-post',
        data: 'p=' + pid,

        success: function(data) {
            $('#te-' + wnd_id).html(data.i);
            $('#prv-' + wnd_id).html(data.o);
            selectTags($('select#tags-' + wnd_id), decodeTagString(data.tg));
            $('#te-' + wnd_id).trigger('input');
            $('#rnd-' + wnd_id + ' label').removeClass('active');
            var t = data.t || _g.Transform;
            $('#rnd-' + wnd_id + '-' + t).addClass('active');
        }
    });
}

function submitEdit(wnd_id) {

    var sel = $('#rnd-' + wnd_id + ' label.active input').val();
    var txt = encodeURIComponent($('#' + wnd_id + ' textarea').val());
    var tags = encodeTagString($('#tags-' + wnd_id).val());
    var pid = _d.windows[wnd_id].pid;

    $.ajax({
        type: 'POST',
        dataType: 'json',
        global: false,
        url: '/update-post',
        data: 'p=' + pid + '&r=' + sel + '&t=' + txt + '&tg=' + tags,

        success: function(data) {
            var d = _d.windows[wnd_id];

            closeEdit(wnd_id);
            $('#c-' + pid).html(data.o);
            $('#tg-' + pid).html(tagsHtml(decodeTagString(data.tg)));
        },
        error: function() {
            Dialog.alert({
                size: BootstrapDialog.SIZE_LARGE,
                type: BootstrapDialog.TYPE_DANGER,
                title: 'Warning',
                message: 'Could not edit (server error).',
            });
        }
    });
}

function quote(pid, on) {
    var num = '#q-' + pid;
    if(on) {
        if(!$(num).data('quote-open')) {
            $(num).data('quote-open', true).
                data('old-color', $(num).css('color'));
            $(num).css('color', colFromId(_g.ColourId));
        }
    } else {
        if($(num).data('quote-open')) {
            $(num).data('quote-open', false);
            $(num).css('color', $(num).data('old-color'));
        }
    }
}

function checkSelection() {

    var sel = document.getSelection();
    if(sel && sel.rangeCount > 0) {

        var rng = sel.getRangeAt(0);
        if( rng && !rng.collapsed) {
            if( (!_copy.range ||
                    (rng.compareBoundaryPoints(Range.START_TO_START, _copy.range) != 0) ||
                    (rng.compareBoundaryPoints(Range.END_TO_END, _copy.range) != 0)) ) {

                var node = rng.commonAncestorContainer;
                var ele = node;
                if(Node.ELEMENT_NODE !== node.nodeType) {
                    ele = node.parentElement;
                }
                while(node && !node.id) {
                    node = node.parentElement;
                }
                if(node && node.id.match(/c-[0-9]+/)) {
                    var got;
                    var el = document.createElement(ele.tagName);
                    el.appendChild(rng.cloneContents());

                    for(var i = 0; i < el.children.length; i++) {
                        var e = el.children[i];
                        for(var j = 0; j < e.attributes.length; j++) {
                            e.removeAttribute(e.attributes[j].name);
                        }
                    }
                    switch(ele.tagName) {
                        case 'DIV': case 'P': case 'SPAN':
                        case 'div': case 'p': case 'span': {
                            got = el.innerHTML;
                            break;
                        }
                        default: {
                            got = el.outerHTML;
                            break;
                        }
                    }
                    _copy.buffer = got;
                    _copy.id = node.id.replace('c-', '');
                    _copy.user = node.getAttribute('data-author');
                    quote(_copy.id, true);
                }
                _copy.range = rng;
            }
        } else {
            if(_copy.id) {
                quote(_copy.id, false);
            }
            _copy.range = undefined;
            _copy.buffer = undefined;
            _copy.id = undefined;
            _copy.user = undefined;
        }
    }
}

function thumbsup(btn, pid) {
    var cnt = '#tc-' + pid;
    if($(btn).data('thumbed')) {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/thumb',
            data: 'p=' + pid,

            success: function(data) {
                $(btn).data('thumbed', false).
                    css('color', $(btn).data('old-color')).
                    attr('class', 'fa fa-fw fa-thumbs-o-up');
                var c = $(cnt).text();
                if(c > 0) { c--; }
                $(cnt).text(c);
            }
        });
    } else {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/thumb',
            data: 'p=' + pid + '&on=true',

            success: function(data) {
                $(btn).data('thumbed', true).
                    data('old-color', $(btn).css('color')).
                    css('color', colFromId(_g.ColourId)).
                    attr('class', 'fa fa-fw fa-thumbs-up');
                var c = $(cnt).text();
                c++;
                $(cnt).text(c);
            }
        });
    }
}

function star(btn, pid) {
    var cnt = '#bc-' + pid;
    if($(btn).data('starred')) {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/star',
            data: 'p=' + pid,

            success: function(data) {
                $(btn).data('starred', false).
                    css('color', $(btn).data('old-color')).
                    attr('class', 'fa fa-fw fa-star-o');
                var c = $(cnt).text();
                if(c > 0) { c--; }
                $(cnt).text(c);
            }
        });
    } else {
        $.ajax({
            type: 'POST',
            dataType: 'text',
            global: false,
            url: '/star',
            data: 'p=' + pid + '&on=true',

            success: function(data) {
                $(btn).data('starred', true).
                    data('old-color', $(btn).css('color')).
                    css('color', colFromId(_g.ColourId)).
                    attr('class', 'fa fa-fw fa-star');
                var c = $(cnt).text();
                c++;
                $(cnt).text(c);
            }
        });
    }
}

function initThreadPage(thid, transform, from, to) {
    _g.ThreadId = thid;
    _g.Transform = transform;
    _g.From = from;
    _g.To = to;

    switch(transform) {
        case 'bbcode': {
            _d.bb_active = 'active';
            break;
        }
        case 'textile': {
            _d.tx_active = 'active';
            break;
        }
        default: {
            _d.hs_active = 'active';
            break;
        }
    }

    $('span[data-thumbed="true"], span[data-starred="true"]').each(function () {
        $(this).css('color', colFromId(_g.ColourId));
    });
}

// insert tag array into the select2 object as selected options, so the tags
// field is populated properly...
function selectTags(select, tags) {
    for(var i = 0; i < tags.length; i++) {
        var opt = $('<option></option>').
            attr('selected', true).text(tags[i]).val(tags[i]);
        opt.appendTo(select);
    }
    select.trigger('change');
}

// FIXME?
function delaySetFocus(wnd_id) {
    setTimeout(function() {
        $('#te-' + wnd_id).focus();
    }, 100);
}

function keyHandler(wnd_id, suppress_open_close) {
    return function(e) {
        if(e.ctrlKey) {
            switch(e.keyCode) {
                case 13: { //'Enter'
                    if(!suppress_open_close) {
                        $('#btn-' + wnd_id + '-submit').click();
                        e.preventDefault();
                    }
                    break;
                }
                case 80: { //'P'
                    $('#btn-' + wnd_id + '-preview').click();
                    e.preventDefault();
                    break;
                }
                case 85: { //'U'
                    $('#btn-' + wnd_id + '-bb').click();
                    e.preventDefault();
                    break;
                }
                case 73: { //'I'
                    $('#btn-' + wnd_id + '-tx').click();
                    e.preventDefault();
                    break;
                }
                case 79: { //'O'
                    $('#btn-' + wnd_id + '-hs').click();
                    e.preventDefault();
                    break;
                }
            }
        }
        if(e.keyCode === 27) {
            if(!suppress_open_close) {
                $('#btn-' + wnd_id + '-cancel').click();
                e.preventDefault();
            }
        }
    }
}

function replyFirst() {
    openWindow('#0', 'r0', 'reply', function() {
        $('#0').remove();
        $('#rnd-r0 label').removeClass('active');
        $('#rnd-r0-' + _g.Transform).addClass('active');
        _d.windows['r0'] = {
            btn_id: '',
            num: '#0',
            pid: 'X',
        }
        delaySetFocus('r0');
    });
}

// install handler for (hacked) keypress events from select2 elements
$(document).ready(function() {
    $('body').on("s2_keypress", function(_, e) {
        var wnd_id = e.id.replace("select2-tags-", "");
        keyHandler(wnd_id, e.open)(e);
    });

    setInterval(checkSelection, 350);

    document.addEventListener('copy', function(e) {
        checkSelection();

        if(_copy.user && _copy.buffer) {
            var buf = '<q><cite>' + _copy.user + '</cite>' + _copy.buffer + '</q>';
            e.clipboardData.clearData('text/plain');
            e.clipboardData.setData('text/plain', buf);
            e.preventDefault();
        }
        return true;
    });
})
<|endoftext|>