<commit_before>/* Cascading-reactor defs & helpers */

import { isCallable } from './utils.js';

// Symbols for private properties
const _value = Symbol('_value');
const _then = Symbol('_then');
const _finally = Symbol('_finally');
const _done = Symbol('_done');
const _persist = Symbol('_persist');

// Parent->children (weak) map
const _children = new WeakMap();

export class Reactor {
	constructor(newval, thenfn, finalfn) {
		// Params thenfn and finalfn are optional
		// Must be functions if given, however

		if ((thenfn === undefined) || (thenfn === null)) {
			this[_then] = null;
		}
		else if (isCallable(thenfn)) {
			this[_then] = thenfn;
		}
		else {
			throw new Error("Param 'thenfn' must be a function");
		}

		if ((finalfn === undefined) || (finalfn === null)) {
			this[_finally] = null;
		}
		else if (isCallable(finalfn)) {
			this[_finally] = finalfn;
		}
		else {
			throw new Error("Param 'finalfn' must be a function");
		}

		// Initial value
		var initval;

		// If newval is another Reactor, set it as parent
		if (newval instanceof Reactor) {
			// Add this to parent's child set
			_children.get(newval).add(this);
			// Get initial value from parent
			initval = newval.value;
		}
		else {
			// Newval is standalone, no parent
			// Use provided initial value
			initval = newval;
		}

		// Start with empty child list
		_children.set(this, new Set());

		// Function thenfn run once if present
		// and given/parent value isn't undefined
		if ((initval !== undefined) && (this[_then] !== null)) {
			this[_value] = this[_then](initval);
		}
		else {
			// Nothing to run or nothing to be run on
			// Value will be undefined if initval is
			this[_value] = initval;
		}

		// Set active, default non-persistent
		this[_persist] = false;
		this[_done] = false;
	}

	get value () {
		return this[_value];
	}

	get done () {
		return this[_done];
	}

	get persistent () {
		return this[_persist];
	}

	get children () {
		return Array.from(_children.get(this));
	}

	set (val) {
		if (this[_done]) {
			return this;
		}
		var oldval = this[_value];
		// Only run thenfn if val not undefined
		var newval = ((val !== undefined) && (this[_then] !== null)) ? this[_then](val, oldval) : val;
		// Set and pass along the raw value instead if newval is undefined
		newval = (newval !== undefined) ? newval : val;

		// Only triggers cascade if value actually changed
		if (newval !== oldval) {
			this[_value] = newval;

			// Autocancel
			var cancelled = [];
			var children = _children.get(this);

			for (var child of children) {
				child = child.set(newval);

				if (child.done) {
					cancelled.push(child);
				}
			}
			var len = cancelled.length;
			if (len > 0) {
				if ((len == children.size) && (!this[_persist])) {
					// Cancel self if all children cancelled
					return this.cancel(newval);
				}
				else {
					// Cull cancelled children
					for (var i = 0; i < len; i++) {
						children.delete(cancelled[i]);
					}
				}
			}
		}
		return this;
	}

	// Returns new reactor with given thenfn and/or finalfn
	then (thenfn, finalfn) {
		if (this[_done]) {
			throw new Error("Cannot cascade from cancelled");
		}
		return new Reactor(this, thenfn, finalfn);
	}

	// Returns new reactor with no thenfn and given finalfn
	finally (finalfn) {
		if (this[_done]) {
			throw new Error("Cannot cascade from cancelled");
		}
		return new Reactor(this, null, finalfn);
	}

	// Cancels reactor and cascades
	cancel (final) {
		if (this[_done]) {
			return this;
		}
		// If finalfn is set, will be called with (final, value) before
		// passing result downward
		var finalval = (this[_finally] !== null) ? this[_finally](final, this[_value]) : final;
		// Reset finalval to val if _finally() returned undefined
		finalval = (finalval !== undefined) ? finalval : final;
		this[_value] = finalval;

		var children = _children.get(this);
		if (children !== undefined) {
			// Cascade to children (set skipdel to true regardless of passed arg)
			for (var child of children) {
				child.cancel(finalval);
			}
		}

		// Clear thenfn/finalfn, and mark cancelled
		this[_then] = null;
		this[_finally] = null;
		this[_done] = true;
		return this;
	}

	// Generator style (for later)
	return () {
		return this.cancel();
	}

	// Attach this to new parent
	// If skipset is true and parent is a Reactor, will skip setting value to parent's
	attach (parent, skipset) {
		if (this[_done]) {
			throw new Error("Cannot attach() cancelled");
		}
		if (parent instanceof Reactor) {
			// Set new parent, add this to new parent's child set, and recalculate value
			_children.get(parent).add(this);
			// Will invoke setter and thus cascade if appropriate
			if (skipset) {
				return this;
			}
			return this.set(parent.value);
		}
		// This is now top of tree, set value and cascade as necessary
		return this.set(parent);
	}

	// Detach this from parent without auto-cancel
	detach (parent) {
		_children.get(parent).delete(this);
		return this;
	}

	// Clear children without self auto-cancel and optionally cancel children
	// Returns array of now-former children
	clear (cancel, final) {
		var children = this.children;
		if (children !== undefined) {
			// Clear child set
			_children.get(this).clear();
			// Explicitly cancel children if requested
			if (cancel) {
				for (var i = 0, len = children.length; i < len; i++) {
					children[i].cancel(final);
				}
			}
		}
		return children;
	}

	// Must be called with explicit false to remove persistence
	// Undefined considered implicit true here
	persist (per) {
		this[_persist] = ((per === undefined) || (!!per));
		return this;
	}

}

// Shortcut for 'new Reactor()'
export function cr (initval, thenfn, finalfn) {
	return new Reactor(initval, thenfn, finalfn);
}

// Creates new Reactor with multiple parents, with value equal to last-set parent
// Initial value will be undefined
export function crAny (...parents) {
	var newcr = new Reactor();
	for (let parent of parents) {
		if (parent instanceof Reactor) {
			newcr.attach(parent, true);
		}
	}
	return newcr;
}

// Creates new reactor with multiple parents, with value equal to array of parent values
export function crAll (...parents) {
	var sources = [];
	var newcr = new Reactor(undefined, () => {
		let arr = [];
		for (let source of sources) {
			arr.push(source.value);
		}
		return arr;
	});
	for (let parent of parents) {
		if (parent instanceof Reactor) {
			// Attach to parent
			newcr.attach(parent, true);
			sources.push(parent)
		}
		else {
			// Convert raw values to container objects
			sources.push({value: parent});
		}
	}
	// Get initial values from parents
	newcr.set(true);
	return newcr;
}
<commit_msg>Added done check to attach(); crAll will include parent objects with a .value prop<commit_after>/* Cascading-reactor defs & helpers */

import { isCallable } from './utils.js';

// Symbols for private properties
const _value = Symbol('_value');
const _then = Symbol('_then');
const _finally = Symbol('_finally');
const _done = Symbol('_done');
const _persist = Symbol('_persist');

// Parent->children (weak) map
const _children = new WeakMap();

export class Reactor {
	constructor(newval, thenfn, finalfn) {
		// Params thenfn and finalfn are optional
		// Must be functions if given, however

		if ((thenfn === undefined) || (thenfn === null)) {
			this[_then] = null;
		}
		else if (isCallable(thenfn)) {
			this[_then] = thenfn;
		}
		else {
			throw new Error("Param 'thenfn' must be a function");
		}

		if ((finalfn === undefined) || (finalfn === null)) {
			this[_finally] = null;
		}
		else if (isCallable(finalfn)) {
			this[_finally] = finalfn;
		}
		else {
			throw new Error("Param 'finalfn' must be a function");
		}

		// Initial value
		var initval;

		// If newval is another Reactor, set it as parent
		if (newval instanceof Reactor) {
			// Add this to parent's child set
			_children.get(newval).add(this);
			// Get initial value from parent
			initval = newval.value;
		}
		else {
			// Newval is standalone, no parent
			// Use provided initial value
			initval = newval;
		}

		// Start with empty child list
		_children.set(this, new Set());

		// Function thenfn run once if present
		// and given/parent value isn't undefined
		if ((initval !== undefined) && (this[_then] !== null)) {
			this[_value] = this[_then](initval);
		}
		else {
			// Nothing to run or nothing to be run on
			// Value will be undefined if initval is
			this[_value] = initval;
		}

		// Set active, default non-persistent
		this[_persist] = false;
		this[_done] = false;
	}

	get value () {
		return this[_value];
	}

	get done () {
		return this[_done];
	}

	get persistent () {
		return this[_persist];
	}

	get children () {
		return Array.from(_children.get(this));
	}

	set (val) {
		if (this[_done]) {
			return this;
		}
		var oldval = this[_value];
		// Only run thenfn if val not undefined
		var newval = ((val !== undefined) && (this[_then] !== null)) ? this[_then](val, oldval) : val;
		// Set and pass along the raw value instead if newval is undefined
		newval = (newval !== undefined) ? newval : val;

		// Only triggers cascade if value actually changed
		if (newval !== oldval) {
			this[_value] = newval;

			// Autocancel
			var cancelled = [];
			var children = _children.get(this);

			for (var child of children) {
				child = child.set(newval);

				if (child.done) {
					cancelled.push(child);
				}
			}
			var len = cancelled.length;
			if (len > 0) {
				if ((len == children.size) && (!this[_persist])) {
					// Cancel self if all children cancelled
					return this.cancel(newval);
				}
				else {
					// Cull cancelled children
					for (var i = 0; i < len; i++) {
						children.delete(cancelled[i]);
					}
				}
			}
		}
		return this;
	}

	// Returns new reactor with given thenfn and/or finalfn
	then (thenfn, finalfn) {
		if (this[_done]) {
			throw new Error("Cannot cascade from cancelled");
		}
		return new Reactor(this, thenfn, finalfn);
	}

	// Returns new reactor with no thenfn and given finalfn
	finally (finalfn) {
		if (this[_done]) {
			throw new Error("Cannot cascade from cancelled");
		}
		return new Reactor(this, null, finalfn);
	}

	// Cancels reactor and cascades
	cancel (final) {
		if (this[_done]) {
			return this;
		}
		// If finalfn is set, will be called with (final, value) before
		// passing result downward
		var finalval = (this[_finally] !== null) ? this[_finally](final, this[_value]) : final;
		// Reset finalval to val if _finally() returned undefined
		finalval = (finalval !== undefined) ? finalval : final;
		this[_value] = finalval;

		var children = _children.get(this);
		if (children !== undefined) {
			// Cascade to children (set skipdel to true regardless of passed arg)
			for (var child of children) {
				child.cancel(finalval);
			}
		}

		// Clear thenfn/finalfn, and mark cancelled
		this[_then] = null;
		this[_finally] = null;
		this[_done] = true;
		return this;
	}

	// Generator style (for later)
	return () {
		return this.cancel();
	}

	// Attach this to new parent
	// If skipset is true and parent is a Reactor, will skip setting value to parent's
	attach (parent, skipset) {
		if (this[_done]) {
			throw new Error("Cannot attach() cancelled Reactor");
		}
		if (parent instanceof Reactor) {
			if (parent.done) {
				throw new Error('Cannot attach() to cancelled Reactor');
			}
			// Set new parent, add this to new parent's child set, and recalculate value
			_children.get(parent).add(this);
			// Will invoke setter and thus cascade if appropriate
			if (skipset) {
				return this;
			}
			return this.set(parent.value);
		}
		// This is now top of tree, set value and cascade as necessary
		return this.set(parent);
	}

	// Detach this from parent without auto-cancel
	detach (parent) {
		_children.get(parent).delete(this);
		return this;
	}

	// Clear children without self auto-cancel and optionally cancel children
	// Returns array of now-former children
	clear (cancel, final) {
		var children = this.children;
		if (children !== undefined) {
			// Clear child set
			_children.get(this).clear();
			// Explicitly cancel children if requested
			if (cancel) {
				for (var i = 0, len = children.length; i < len; i++) {
					children[i].cancel(final);
				}
			}
		}
		return children;
	}

	// Must be called with explicit false to remove persistence
	// Undefined considered implicit true here
	persist (per) {
		this[_persist] = ((per === undefined) || (!!per));
		return this;
	}

}

// Shortcut for 'new Reactor()'
export function cr (initval, thenfn, finalfn) {
	return new Reactor(initval, thenfn, finalfn);
}

// Creates new Reactor with multiple parents, with value equal to last-set parent
// Initial value will be undefined
export function crAny (...parents) {
	var newcr = new Reactor();
	for (let parent of parents) {
		if (parent instanceof Reactor) {
			newcr.attach(parent, true);
		}
	}
	return newcr;
}

// Creates new reactor with multiple parents, with value equal to array of parent values
// Parents which are reactors will be attached to, and each set() will update the returned array
// Parents which are objects with a .value property will be included by reference
// Parents which are raw values will be included in output as constants
export function crAll (...parents) {
	var sources = [];
	var newcr = new Reactor(undefined, () => {
		let arr = [];
		for (let source of sources) {
			arr.push(source.value);
		}
		return arr;
	});
	for (let parent of parents) {
		if (parent instanceof Reactor) {
			// Attach to parent
			newcr.attach(parent, true);
			sources.push(parent)
		}
		else if ('value' in parent) {
			sources.push(parent);
		}
		else {
			// Convert raw values to container objects
			sources.push({value: parent});
		}
	}
	// Get initial values from parents
	newcr.set(true);
	return newcr;
}
<|endoftext|>


<commit_before>import * as debug from './debug'
import { ActionTypes } from './actions'
import { parseActions, isHistory, newHistory } from './helpers'

// createHistory
function createHistory (state, ignoreInitialState) {
  // ignoreInitialState essentially prevents the user from undoing to the
  // beginning, in the case that the undoable reducer handles initialization
  // in a way that can't be redone simply
  const history = newHistory([], state, [])
  if (ignoreInitialState) {
    history._latestUnfiltered = null
  }
  return history
}

// lengthWithoutFuture: get length of history
function lengthWithoutFuture (history) {
  return history.past.length + 1
}

// insert: insert `state` into history, which means adding the current state
//         into `past`, setting the new `state` as `present` and erasing
//         the `future`.
function insert (history, state, limit, group) {
  debug.log('inserting', state)
  debug.log('new free: ', limit - lengthWithoutFuture(history))

  const { past, _latestUnfiltered } = history
  const historyOverflow = limit && lengthWithoutFuture(history) >= limit

  const pastSliced = past.slice(historyOverflow ? 1 : 0)
  const newPast = _latestUnfiltered != null
    ? [
      ...pastSliced,
      _latestUnfiltered
    ] : pastSliced

  return newHistory(newPast, state, [], group)
}

// jumpToFuture: jump to requested index in future history
function jumpToFuture (history, index) {
  if (index < 0 || index >= history.future.length) return history

  const { past, future, _latestUnfiltered } = history

  const newPast = [...past, _latestUnfiltered, ...future.slice(0, index)]
  const newPresent = future[index]
  const newFuture = future.slice(index + 1)

  return newHistory(newPast, newPresent, newFuture)
}

// jumpToPast: jump to requested index in past history
function jumpToPast (history, index) {
  if (index < 0 || index >= history.past.length) return history

  const { past, future, _latestUnfiltered } = history

  const newPast = past.slice(0, index)
  const newFuture = [...past.slice(index + 1), _latestUnfiltered, ...future]
  const newPresent = past[index]

  return newHistory(newPast, newPresent, newFuture)
}

// jump: jump n steps in the past or forward
function jump (history, n) {
  if (n > 0) return jumpToFuture(history, n - 1)
  if (n < 0) return jumpToPast(history, history.past.length + n)
  return history
}

// helper to dynamically match in the reducer's switch-case
function actionTypeAmongClearHistoryType (actionType, clearHistoryType) {
  return clearHistoryType.indexOf(actionType) > -1 ? actionType : !actionType
}

// redux-undo higher order reducer
export default function undoable (reducer, rawConfig = {}) {
  debug.set(rawConfig.debug)

  const config = {
    limit: undefined,
    filter: () => true,
    groupBy: () => null,
    undoType: ActionTypes.UNDO,
    redoType: ActionTypes.REDO,
    jumpToPastType: ActionTypes.JUMP_TO_PAST,
    jumpToFutureType: ActionTypes.JUMP_TO_FUTURE,
    jumpType: ActionTypes.JUMP,
    neverSkipReducer: false,
    ignoreInitialState: false,
    syncFilter: false,

    ...rawConfig,

    initTypes: parseActions(rawConfig.initTypes, ['@@redux-undo/INIT']),
    clearHistoryType: parseActions(
      rawConfig.clearHistoryType,
      [ActionTypes.CLEAR_HISTORY]
    )
  }

  let initialState
  return (state = initialState, action = {}, ...slices) => {
    debug.start(action, state)

    let history = state
    if (!initialState) {
      debug.log('history is uninitialized')

      if (state === undefined) {
        const createHistoryAction = { type: '@@redux-undo/CREATE_HISTORY' }
        const start = reducer(state, createHistoryAction, ...slices)

        history = createHistory(
          start,
          config.ignoreInitialState
        )

        debug.log('do not set initialState on probe actions')
        debug.end(history)
        return history
      } else if (isHistory(state)) {
        history = initialState = config.ignoreInitialState
          ? state : newHistory(
            state.past,
            state.present,
            state.future
          )
        debug.log(
          'initialHistory initialized: initialState is a history',
          initialState
        )
      } else {
        history = initialState = createHistory(
          state,
          config.ignoreInitialState
        )
        debug.log(
          'initialHistory initialized: initialState is not a history',
          initialState
        )
      }
    }

    const skipReducer = (res) => config.neverSkipReducer
      ? {
        ...res,
        present: reducer(res.present, action, ...slices)
      } : res

    let res
    switch (action.type) {
      case undefined:
        return history

      case config.undoType:
        res = jump(history, -1)
        debug.log('perform undo')
        debug.end(res)
        return skipReducer(res)

      case config.redoType:
        res = jump(history, 1)
        debug.log('perform redo')
        debug.end(res)
        return skipReducer(res)

      case config.jumpToPastType:
        res = jumpToPast(history, action.index)
        debug.log(`perform jumpToPast to ${action.index}`)
        debug.end(res)
        return skipReducer(res)

      case config.jumpToFutureType:
        res = jumpToFuture(history, action.index)
        debug.log(`perform jumpToFuture to ${action.index}`)
        debug.end(res)
        return skipReducer(res)

      case config.jumpType:
        res = jump(history, action.index)
        debug.log(`perform jump to ${action.index}`)
        debug.end(res)
        return skipReducer(res)

      case actionTypeAmongClearHistoryType(action.type, config.clearHistoryType):
        res = createHistory(history.present, config.ignoreInitialState)
        debug.log('perform clearHistory')
        debug.end(res)
        return skipReducer(res)

      default:
        res = reducer(
          history.present,
          action,
          ...slices
        )

        if (config.initTypes.some((actionType) => actionType === action.type)) {
          debug.log('reset history due to init action')
          debug.end(initialState)
          return initialState
        }

        if (history._latestUnfiltered === res) {
          // Don't handle this action. Do not call debug.end here,
          // because this action should not produce side effects to the console
          return history
        }

        /* eslint-disable-next-line no-case-declarations */
        const filtered = typeof config.filter === 'function' && !config.filter(
          action,
          res,
          history
        )

        if (filtered) {
          // if filtering an action, merely update the present
          const filteredState = newHistory(
            history.past,
            res,
            history.future,
            history.group
          )
          if (!config.syncFilter) {
            filteredState._latestUnfiltered = history._latestUnfiltered
          }
          debug.log('filter ignored action, not storing it in past')
          debug.end(filteredState)
          return filteredState
        }

        /* eslint-disable-next-line no-case-declarations */
        const group = config.groupBy(action, res, history)
        if (group != null && group === history.group) {
          // if grouping with the previous action, only update the present
          const groupedState = newHistory(
            history.past,
            res,
            history.future,
            history.group
          )
          debug.log('groupBy grouped the action with the previous action')
          debug.end(groupedState)
          return groupedState
        }

        // If the action wasn't filtered or grouped, insert normally
        history = insert(history, res, config.limit, group)

        debug.log('inserted new state into history')
        debug.end(history)
        return history
    }
  }
}
<commit_msg>Moved skipReducer to initialization stage<commit_after>import * as debug from './debug'
import { ActionTypes } from './actions'
import { parseActions, isHistory, newHistory } from './helpers'

// createHistory
function createHistory (state, ignoreInitialState) {
  // ignoreInitialState essentially prevents the user from undoing to the
  // beginning, in the case that the undoable reducer handles initialization
  // in a way that can't be redone simply
  const history = newHistory([], state, [])
  if (ignoreInitialState) {
    history._latestUnfiltered = null
  }
  return history
}

// lengthWithoutFuture: get length of history
function lengthWithoutFuture (history) {
  return history.past.length + 1
}

// insert: insert `state` into history, which means adding the current state
//         into `past`, setting the new `state` as `present` and erasing
//         the `future`.
function insert (history, state, limit, group) {
  debug.log('inserting', state)
  debug.log('new free: ', limit - lengthWithoutFuture(history))

  const { past, _latestUnfiltered } = history
  const historyOverflow = limit && lengthWithoutFuture(history) >= limit

  const pastSliced = past.slice(historyOverflow ? 1 : 0)
  const newPast = _latestUnfiltered != null
    ? [
      ...pastSliced,
      _latestUnfiltered
    ] : pastSliced

  return newHistory(newPast, state, [], group)
}

// jumpToFuture: jump to requested index in future history
function jumpToFuture (history, index) {
  if (index < 0 || index >= history.future.length) return history

  const { past, future, _latestUnfiltered } = history

  const newPast = [...past, _latestUnfiltered, ...future.slice(0, index)]
  const newPresent = future[index]
  const newFuture = future.slice(index + 1)

  return newHistory(newPast, newPresent, newFuture)
}

// jumpToPast: jump to requested index in past history
function jumpToPast (history, index) {
  if (index < 0 || index >= history.past.length) return history

  const { past, future, _latestUnfiltered } = history

  const newPast = past.slice(0, index)
  const newFuture = [...past.slice(index + 1), _latestUnfiltered, ...future]
  const newPresent = past[index]

  return newHistory(newPast, newPresent, newFuture)
}

// jump: jump n steps in the past or forward
function jump (history, n) {
  if (n > 0) return jumpToFuture(history, n - 1)
  if (n < 0) return jumpToPast(history, history.past.length + n)
  return history
}

// helper to dynamically match in the reducer's switch-case
function actionTypeAmongClearHistoryType (actionType, clearHistoryType) {
  return clearHistoryType.indexOf(actionType) > -1 ? actionType : !actionType
}

// redux-undo higher order reducer
export default function undoable (reducer, rawConfig = {}) {
  debug.set(rawConfig.debug)

  const config = {
    limit: undefined,
    filter: () => true,
    groupBy: () => null,
    undoType: ActionTypes.UNDO,
    redoType: ActionTypes.REDO,
    jumpToPastType: ActionTypes.JUMP_TO_PAST,
    jumpToFutureType: ActionTypes.JUMP_TO_FUTURE,
    jumpType: ActionTypes.JUMP,
    neverSkipReducer: false,
    ignoreInitialState: false,
    syncFilter: false,

    ...rawConfig,

    initTypes: parseActions(rawConfig.initTypes, ['@@redux-undo/INIT']),
    clearHistoryType: parseActions(
      rawConfig.clearHistoryType,
      [ActionTypes.CLEAR_HISTORY]
    )
  }

  // Allows the user to call the reducer with redux-undo specific actions
  const skipReducer = config.neverSkipReducer
    ? (res, action, ...slices) => ({
      ...res,
      present: reducer(res.present, action, ...slices)
    })
    : (res) => res

  let initialState
  return (state = initialState, action = {}, ...slices) => {
    debug.start(action, state)

    let history = state
    if (!initialState) {
      debug.log('history is uninitialized')

      if (state === undefined) {
        const createHistoryAction = { type: '@@redux-undo/CREATE_HISTORY' }
        const start = reducer(state, createHistoryAction, ...slices)

        history = createHistory(
          start,
          config.ignoreInitialState
        )

        debug.log('do not set initialState on probe actions')
        debug.end(history)
        return history
      } else if (isHistory(state)) {
        history = initialState = config.ignoreInitialState
          ? state : newHistory(
            state.past,
            state.present,
            state.future
          )
        debug.log(
          'initialHistory initialized: initialState is a history',
          initialState
        )
      } else {
        history = initialState = createHistory(
          state,
          config.ignoreInitialState
        )
        debug.log(
          'initialHistory initialized: initialState is not a history',
          initialState
        )
      }
    }

    let res
    switch (action.type) {
      case undefined:
        return history

      case config.undoType:
        res = jump(history, -1)
        debug.log('perform undo')
        debug.end(res)
        return skipReducer(res, action, ...slices)

      case config.redoType:
        res = jump(history, 1)
        debug.log('perform redo')
        debug.end(res)
        return skipReducer(res, action, ...slices)

      case config.jumpToPastType:
        res = jumpToPast(history, action.index)
        debug.log(`perform jumpToPast to ${action.index}`)
        debug.end(res)
        return skipReducer(res, action, ...slices)

      case config.jumpToFutureType:
        res = jumpToFuture(history, action.index)
        debug.log(`perform jumpToFuture to ${action.index}`)
        debug.end(res)
        return skipReducer(res, action, ...slices)

      case config.jumpType:
        res = jump(history, action.index)
        debug.log(`perform jump to ${action.index}`)
        debug.end(res)
        return skipReducer(res, action, ...slices)

      case actionTypeAmongClearHistoryType(action.type, config.clearHistoryType):
        res = createHistory(history.present, config.ignoreInitialState)
        debug.log('perform clearHistory')
        debug.end(res)
        return skipReducer(res, action, ...slices)

      default:
        res = reducer(
          history.present,
          action,
          ...slices
        )

        if (config.initTypes.some((actionType) => actionType === action.type)) {
          debug.log('reset history due to init action')
          debug.end(initialState)
          return initialState
        }

        if (history._latestUnfiltered === res) {
          // Don't handle this action. Do not call debug.end here,
          // because this action should not produce side effects to the console
          return history
        }

        /* eslint-disable-next-line no-case-declarations */
        const filtered = typeof config.filter === 'function' && !config.filter(
          action,
          res,
          history
        )

        if (filtered) {
          // if filtering an action, merely update the present
          const filteredState = newHistory(
            history.past,
            res,
            history.future,
            history.group
          )
          if (!config.syncFilter) {
            filteredState._latestUnfiltered = history._latestUnfiltered
          }
          debug.log('filter ignored action, not storing it in past')
          debug.end(filteredState)
          return filteredState
        }

        /* eslint-disable-next-line no-case-declarations */
        const group = config.groupBy(action, res, history)
        if (group != null && group === history.group) {
          // if grouping with the previous action, only update the present
          const groupedState = newHistory(
            history.past,
            res,
            history.future,
            history.group
          )
          debug.log('groupBy grouped the action with the previous action')
          debug.end(groupedState)
          return groupedState
        }

        // If the action wasn't filtered or grouped, insert normally
        history = insert(history, res, config.limit, group)

        debug.log('inserted new state into history')
        debug.end(history)
        return history
    }
  }
}
<|endoftext|>


<commit_before>'use strict';
const React = require('react/addons'),
	cloneWithProps = React.addons.cloneWithProps,
	merge = require('merge');

module.exports = merge(replace, {
	by: merge(replaceBy, {
		any: {
			className: replaceByAnyClassName,
		}
	}),
	has: {
		any: {
			className: hasAnyClassName,
		}
	},
});

/** @function replace(transform)(Elements) -> Elements
	@function transform(Element) -> Element
	Applies the transform function if predicate(Element) returns true
	over all nodes in the Elements tree.
*/
function replace(transform) {
	if (typeof transform !== 'function') {
		/** @todo: transform could be a set of new props? */
		throw new Error('replace(transform)(Element) transform must be a function.');
	}
	if (transform && transform._isReactElement) {
		transform = transform.props;
	}
	if (transform && typeof transform === 'object') {
		transform = function (props) {
			return function () {
				return props;
			};
		}(transform);
	}
	return function (Element) {
		var props = properties(transform(Element));
		if (props && props._isReactElement) {
			props = props.props;
		}
		return cloneWithProps(Element, merge({ key: Element.key, ref: Element.ref, }, props));
	}
}

/** @function replace.by(predicate)(transform)(Elements) -> Elements
	@function predicate(Element) -> Boolean like what you pass Array.prototype.filter
	@function transform(Element) -> Element like what you pass Array.prototype.map
	Applies the transform function if predicate(Element) returns true
	over all nodes in the Elements tree.
*/
function replaceBy(predicate) {
	if (typeof predicate !== 'function') {
		/** @todo: predicate could be a string query much like jquery css selectors? */
		throw new Error('replaceBy(predicate)(transform)(Elements) predicate must be a function.');
	}
	function map(transform) {
		var replaceTransform = replace(transform);
		function query(Elements) {
			if (Array.isArray(Elements)) {
				return Elements.map(query);
			}
			if (Elements && Elements._isReactElement && predicate(Elements)) {
				var Element = replaceTransform(Elements);
				if (Element.props.children) {
					Element.props.children = query(Element.props.children);
				}
				return Element;
			}
			return Elements;
		};
		return query;
	};
	return map;
}

/** @function replace.has.any.className(name)(Element) -> Boolean
	Accepts a string of class names separated by spaces,
		and returns a predicate that returns true
		if any of those class names match Element.className
*/
function hasAnyClassName(names) {
	var regex = new RegExp('\b' + names.replace(/^\W|\W$/g, '').replace(/\W+/g, '\b|\b') + '\b', 'i');
	return function (Element) {
		return (Element && regex.test(Element.className));
	}
}

/** @function replace.by.any.className({ [names]: transform(Element) -> Element })(Elements) -> Elements
	Applies the transforms to all nodes in the Elements tree
		if any of those class names match Element.className
*/
replaceByAnyClassName(namesToTransforms) {
	var transforms = Object.key(namesToTransforms).
		reduce(function (transforms, names) {
			var predicate = hasAnyClassName(names),
				transform = namesToTransforms[names];
			return function (Element) {
				if (Element && Element._isReactElement && predicate(Element)) {
					return transforms(transform(Element));
				}
				return Element;
			};
		}, function (Element) { return Element; });
	return replaceBy(function (Element) { return true; })(transforms);
}
<commit_msg>:exclamation: too tired to debug.<commit_after>'use strict';
var
	React = require('react/addons'),
	cloneWithProps = React.addons.cloneWithProps,
	merge = require('merge');

module.exports = merge(replace, {
	by: merge(replaceBy, {
		any: {
			className: replaceByAnyClassName,
		}
	}),
	has: {
		any: {
			className: hasAnyClassName,
		}
	},
});

/** @function replace(transform)(Elements) -> Elements
	@function transform(Element) -> Element
	Applies the transform function if predicate(Element) returns true
	over all nodes in the Elements tree.
*/
function replace(transform) {
	if (typeof transform !== 'function') {
		/** @todo: transform could be a set of new props? */
		throw new Error('replace(transform)(Element) transform must be a function.');
	}
	if (transform && transform._isReactElement) {
		transform = transform.props;
	}
	if (transform && typeof transform === 'object') {
		transform = function (props) {
			return function () {
				return props;
			};
		}(transform);
	}
	return function (Element) {
		var props = properties(transform(Element));
		if (props && props._isReactElement) {
			props = props.props;
		}
		return cloneWithProps(Element, merge({ key: Element.key, ref: Element.ref, }, props));
	}
}

/** @function replace.by(predicate)(transform)(Elements) -> Elements
	@function predicate(Element) -> Boolean like what you pass Array.prototype.filter
	@function transform(Element) -> Element like what you pass Array.prototype.map
	Applies the transform function if predicate(Element) returns true
	over all nodes in the Elements tree.
*/
function replaceBy(predicate) {
	if (typeof predicate !== 'function') {
		/** @todo: predicate could be a string query much like jquery css selectors? */
		throw new Error('replaceBy(predicate)(transform)(Elements) predicate must be a function.');
	}
	function map(transform) {
		var replaceTransform = replace(transform);
		function query(Elements) {
			if (Array.isArray(Elements)) {
				return Elements.map(query);
			}
			if (Elements && Elements._isReactElement && predicate(Elements)) {
				var Element = replaceTransform(Elements);
				if (Element.props.children) {
					Element.props.children = query(Element.props.children);
				}
				return Element;
			}
			return Elements;
		};
		return query;
	};
	return map;
}

/** @function replace.has.any.className(name)(Element) -> Boolean
	Accepts a string of class names separated by spaces,
		and returns a predicate that returns true
		if any of those class names match Element.className
*/
function hasAnyClassName(names) {
	var regex = new RegExp('\b' + names.replace(/^\W|\W$/g, '').replace(/\W+/g, '\b|\b') + '\b', 'i');
	return function (Element) {
		return (Element && regex.test(Element.className));
	}
}

/** @function replace.by.any.className({ [names]: transform(Element) -> Element })(Elements) -> Elements
	Applies the transforms to all nodes in the Elements tree
		if any of those class names match Element.className
*/
function replaceByAnyClassName(namesToTransforms) {
	var transforms = Object.keys(namesToTransforms).
		reduce(function (transforms, names) {
			var predicate = hasAnyClassName(names),
				transform = namesToTransforms[names];
			return function (Element) {
				if (Element && Element._isReactElement && predicate(Element)) {
					return transforms(transform(Element));
				}
				return Element;
			};
		}, function (Element) { return Element; });
	return replaceBy(function (Element) { return true; })(transforms);
}
<|endoftext|>


<commit_before>import _last from 'lodash/last'
import isEmpty from 'lodash/isEmpty'
import isNumber from 'lodash/isNumber'
import leaf from './leaf'
import mrDefaultFromCursor from './defaultFromCursor'
import mrDefaultToCursor from './defaultToCursor'
import reverse from 'lodash/reverse'

/**
 * Query and resolve according to the pagination algorithm.
 * ref: https://facebook.github.io/relay/graphql/connections.htm#sec-Pagination-algorithm
 * @param {object} args Arguments from parent value.
 * @param {object} model Mongoose model
 * @param {object} query Mongo query to get all documents.
 * @param {string} cursorField Unique field used in sorting and constructing the cursor.
 * @param {number} direction 1 to sort ascendingly, -1 to sort decendingly.
 * @param {string} populate Mongoose field(s) to be populated.
 * note: if both first and last are given, then last is ignored
 */
async function mrResolve (args, model, query = {}, { cursorField = '_id', direction = 1, toCursor = mrDefaultToCursor, fromCursor = mrDefaultFromCursor, mapNode = x => x, populate = '' } = {}) {
  if (!isNumber(direction)) {
    direction = 1
  }
  const { after, first, before } = args
  let last = args.last

  // if both first and last are given, last is ignored
  if (first && last) {
    last = null
  }

  let sort = {
    [cursorField]: direction
  }

  let idSort = 1
  if (cursorField === '_id') {
    idSort = direction
  }

  let afterQuery = {}
  let beforeQuery = {}

  if (after) {
    const { field, id } = fromCursor(after)
    // Let afterEdge be the edge in edges whose cursor is equal to the after argument.
    // if field is found, if it is unique, then count is 1, otherwise larger than 1.
    const afterEdgeCount = await model.countDocuments({ ...query, [cursorField]: field })

    // Remove all elements of edges before and including afterEdge.
    if (direction === 1) {
      afterQuery[cursorField] = { $gt: field }
    } else {
      afterQuery[cursorField] = { $lt: field }
    }

    // non unique case, need to fetch back the tie-ing docs too
    if (afterEdgeCount > 1) {
      const tie = {
        // ...query,
        [cursorField]: field,
        _id: { $gt: id }
      }
      afterQuery = { $or: [tie, afterQuery] }
    }
  }

  if (before) {
    const { field, id } = fromCursor(before)
    // Let beforeEdge be the edge in edges whose cursor is equal to the before argument.
    const beforeEdgeCount = await model.countDocuments({ ...query, [cursorField]: field })
    // Remove all elements of edges after and including beforeEdge.
    if (direction === 1) {
      beforeQuery[cursorField] = { $lt: field }
    } else {
      beforeQuery[cursorField] = { $gt: field }
    }

    if (beforeEdgeCount > 1) {
      const tie = {
        // ...query,
        [cursorField]: field,
        _id: { $lt: id }
      }
      beforeQuery = { $or: [tie, beforeQuery] }
    }
  }

  // in case cursorField is not unique
  const multiSort = [[cursorField, sort[cursorField]], ['_id', idSort]]
  if (last) {
    multiSort[0][1] = direction * -1
    multiSort[1][1] = -1
  }

  const joinQuery = [
    query,
    afterQuery,
    beforeQuery
  ].filter(x => !isEmpty(x))

  let finalQuery = {}
  if (joinQuery.length > 1) {
    finalQuery = { $and: joinQuery }
  } else if (joinQuery.length === 1) {
    finalQuery = joinQuery[0]
  }

  if (first && first < 0) {
    throw new Error(`first(${first}) could not be negative`)
  }

  if (last && last < 0) {
    throw new Error(`last(${last}) could not be negative`)
  }
  const limit = first || last
  const nodes = await model.find(finalQuery).limit(limit).sort(multiSort).populate(populate)
  let edges = nodes.map(node => {
    return {
      node: mapNode(node),
      cursor: toCursor(leaf(node, cursorField), node.id)
    }
  })
  if (last) {
    edges = reverse(edges)
  }

  const edgesCount = await model.find(finalQuery).countDocuments()

  let hasPreviousPage = false
  if (last && edgesCount > last) {
    hasPreviousPage = true
  }

  let hasNextPage = false
  if (first && edgesCount > first) {
    hasNextPage = true
  }

  let startCursor = ''
  let endCursor = ''
  if (edges.length > 0) {
    startCursor = edges[0].cursor
    endCursor = _last(edges).cursor
  }

  const pageInfo = {
    hasNextPage,
    hasPreviousPage,
    startCursor,
    endCursor
  }

  return {
    pageInfo,
    edges,
    totalCount: model.find(query).countDocuments()
  }
}

export default mrResolve
<commit_msg>:zap: improve performance of counting edges<commit_after>import _last from 'lodash/last'
import isEmpty from 'lodash/isEmpty'
import isNumber from 'lodash/isNumber'
import leaf from './leaf'
import mrDefaultFromCursor from './defaultFromCursor'
import mrDefaultToCursor from './defaultToCursor'
import reverse from 'lodash/reverse'

/**
 * Query and resolve according to the pagination algorithm.
 * ref: https://facebook.github.io/relay/graphql/connections.htm#sec-Pagination-algorithm
 * @param {object} args Arguments from parent value.
 * @param {object} model Mongoose model
 * @param {object} query Mongo query to get all documents.
 * @param {string} cursorField Unique field used in sorting and constructing the cursor.
 * @param {number} direction 1 to sort ascendingly, -1 to sort decendingly.
 * @param {string} populate Mongoose field(s) to be populated.
 * note: if both first and last are given, then last is ignored
 */
async function mrResolve (args, model, query = {}, { cursorField = '_id', direction = 1, toCursor = mrDefaultToCursor, fromCursor = mrDefaultFromCursor, mapNode = x => x, populate = '' } = {}) {
  if (!isNumber(direction)) {
    direction = 1
  }
  const { after, first, before } = args
  let last = args.last

  // if both first and last are given, last is ignored
  if (first && last) {
    last = null
  }

  let sort = {
    [cursorField]: direction
  }

  let idSort = 1
  if (cursorField === '_id') {
    idSort = direction
  }

  let afterQuery = {}
  let beforeQuery = {}

  if (after) {
    const { field, id } = fromCursor(after)
    // Let afterEdge be the edge in edges whose cursor is equal to the after argument.
    // if field is found, if it is unique, then count is 1, otherwise larger than 1.
    const afterEdgeCount = await model.countDocuments({ ...query, [cursorField]: field })

    // Remove all elements of edges before and including afterEdge.
    if (direction === 1) {
      afterQuery[cursorField] = { $gt: field }
    } else {
      afterQuery[cursorField] = { $lt: field }
    }

    // non unique case, need to fetch back the tie-ing docs too
    if (afterEdgeCount > 1) {
      const tie = {
        // ...query,
        [cursorField]: field,
        _id: { $gt: id }
      }
      afterQuery = { $or: [tie, afterQuery] }
    }
  }

  if (before) {
    const { field, id } = fromCursor(before)
    // Let beforeEdge be the edge in edges whose cursor is equal to the before argument.
    const beforeEdgeCount = await model.countDocuments({ ...query, [cursorField]: field })
    // Remove all elements of edges after and including beforeEdge.
    if (direction === 1) {
      beforeQuery[cursorField] = { $lt: field }
    } else {
      beforeQuery[cursorField] = { $gt: field }
    }

    if (beforeEdgeCount > 1) {
      const tie = {
        // ...query,
        [cursorField]: field,
        _id: { $lt: id }
      }
      beforeQuery = { $or: [tie, beforeQuery] }
    }
  }

  // in case cursorField is not unique
  const multiSort = [[cursorField, sort[cursorField]], ['_id', idSort]]
  if (last) {
    multiSort[0][1] = direction * -1
    multiSort[1][1] = -1
  }

  const joinQuery = [
    query,
    afterQuery,
    beforeQuery
  ].filter(x => !isEmpty(x))

  let finalQuery = {}
  if (joinQuery.length > 1) {
    finalQuery = { $and: joinQuery }
  } else if (joinQuery.length === 1) {
    finalQuery = joinQuery[0]
  }

  if (first && first < 0) {
    throw new Error(`first(${first}) could not be negative`)
  }

  if (last && last < 0) {
    throw new Error(`last(${last}) could not be negative`)
  }
  const limit = first || last
  const nodes = await model.find(finalQuery).limit(limit).sort(multiSort).populate(populate)
  let edges = nodes.map(node => {
    return {
      node: mapNode(node),
      cursor: toCursor(leaf(node, cursorField), node.id)
    }
  })
  if (last) {
    edges = reverse(edges)
  }

  // special optimization:
  // if limit (from first or last) is specified,
  // then edgesCount could be limited by a bigger number than limit,
  // instead of counting all docs, to infer if more pages is available.
  let cntLimit
  if (limit) {
    cntLimit = limit + 1
  }
  const edgesCount = await model.find(finalQuery).limit(cntLimit).countDocuments()

  let hasPreviousPage = false
  if (last && edgesCount > last) {
    hasPreviousPage = true
  }

  let hasNextPage = false
  if (first && edgesCount > first) {
    hasNextPage = true
  }

  let startCursor = ''
  let endCursor = ''
  if (edges.length > 0) {
    startCursor = edges[0].cursor
    endCursor = _last(edges).cursor
  }

  const pageInfo = {
    hasNextPage,
    hasPreviousPage,
    startCursor,
    endCursor
  }

  return {
    pageInfo,
    edges,
    totalCount: model.find(query).countDocuments()
  }
}

export default mrResolve
<|endoftext|>


<commit_before>'use strict';

var lib = require('./lib');
var Obj = require('./object');

// Frames keep track of scoping both at compile-time and run-time so
// we know how to access variables. Block tags can introduce special
// variables, for example.
var Frame = Obj.extend({
    init: function(parent, isolateWrites) {
        this.variables = {};
        this.parent = parent;
        this.topLevel = false;
        // if this is true, writes (set) should never propagate upwards past
        // this frame to its parent (though reads may).
        this.isolateWrites = isolateWrites;
    },

    set: function(name, val, resolveUp) {
        // Allow variables with dots by automatically creating the
        // nested structure
        var parts = name.split('.');
        var obj = this.variables;
        var frame = this;

        if(resolveUp) {
            if((frame = this.resolve(parts[0], true))) {
                frame.set(name, val);
                return;
            }
        }

        for(var i=0; i<parts.length - 1; i++) {
            var id = parts[i];

            if(!obj[id]) {
                obj[id] = {};
            }
            obj = obj[id];
        }

        obj[parts[parts.length - 1]] = val;
    },

    get: function(name) {
        var val = this.variables[name];
        if(val !== undefined) {
            return val;
        }
        return null;
    },

    lookup: function(name) {
        var p = this.parent;
        var val = this.variables[name];
        if(val !== undefined) {
            return val;
        }
        return p && p.lookup(name);
    },

    resolve: function(name, forWrite) {
        var p = (forWrite && this.isolateWrites) ? undefined : this.parent;
        var val = this.variables[name];
        if(val !== undefined) {
            return this;
        }
        return p && p.resolve(name);
    },

    push: function(isolateWrites) {
        return new Frame(this, isolateWrites);
    },

    pop: function() {
        return this.parent;
    }
});

function makeMacro(argNames, kwargNames, func) {
    return function() {
        var argCount = numArgs(arguments);
        var args;
        var kwargs = getKeywordArgs(arguments);
        var i;

        if(argCount > argNames.length) {
            args = Array.prototype.slice.call(arguments, 0, argNames.length);

            // Positional arguments that should be passed in as
            // keyword arguments (essentially default values)
            var vals = Array.prototype.slice.call(arguments, args.length, argCount);
            for(i = 0; i < vals.length; i++) {
                if(i < kwargNames.length) {
                    kwargs[kwargNames[i]] = vals[i];
                }
            }

            args.push(kwargs);
        }
        else if(argCount < argNames.length) {
            args = Array.prototype.slice.call(arguments, 0, argCount);

            for(i = argCount; i < argNames.length; i++) {
                var arg = argNames[i];

                // Keyword arguments that should be passed as
                // positional arguments, i.e. the caller explicitly
                // used the name of a positional arg
                args.push(kwargs[arg]);
                delete kwargs[arg];
            }

            args.push(kwargs);
        }
        else {
            args = arguments;
        }

        return func.apply(this, args);
    };
}

function makeKeywordArgs(obj) {
    obj.__keywords = true;
    return obj;
}

function getKeywordArgs(args) {
    var len = args.length;
    if(len) {
        var lastArg = args[len - 1];
        if(lastArg && lastArg.hasOwnProperty('__keywords')) {
            return lastArg;
        }
    }
    return {};
}

function numArgs(args) {
    var len = args.length;
    if(len === 0) {
        return 0;
    }

    var lastArg = args[len - 1];
    if(lastArg && lastArg.hasOwnProperty('__keywords')) {
        return len - 1;
    }
    else {
        return len;
    }
}

// A SafeString object indicates that the string should not be
// autoescaped. This happens magically because autoescaping only
// occurs on primitive string objects.
function SafeString(val) {
    if(typeof val !== 'string') {
        return val;
    }

    this.val = val;
    this.length = val.length;
}

SafeString.prototype = Object.create(String.prototype, {
    length: { writable: true, configurable: true, value: 0 }
});
SafeString.prototype.valueOf = function() {
    return this.val;
};
SafeString.prototype.toString = function() {
    return this.val;
};

function copySafeness(dest, target) {
    if(dest instanceof SafeString) {
        return new SafeString(target);
    }
    return target.toString();
}

function markSafe(val) {
    var type = typeof val;

    if(type === 'string') {
        return new SafeString(val);
    }
    else if(type !== 'function') {
        return val;
    }
    else {
        return function() {
            var ret = val.apply(this, arguments);

            if(typeof ret === 'string') {
                return new SafeString(ret);
            }

            return ret;
        };
    }
}

function suppressValue(val, autoescape) {
    val = (val !== undefined && val !== null) ? val : '';

    if(autoescape && typeof val === 'string') {
        val = lib.escape(val);
    }

    return val;
}

function ensureDefined(val, lineno, colno) {
    if(val === null || val === undefined) {
        throw new lib.TemplateError(
            'attempted to output null or undefined value',
            lineno + 1,
            colno + 1
        );
    }
    return val;
}

function memberLookup(obj, val) {
    obj = obj || {};

    if(typeof obj[val] === 'function') {
        return function() {
            return obj[val].apply(obj, arguments);
        };
    }

    return obj[val];
}

function callWrap(obj, name, context, args) {
    if(!obj) {
        throw new Error('Unable to call `' + name + '`, which is undefined or falsey');
    }
    else if(typeof obj !== 'function') {
        throw new Error('Unable to call `' + name + '`, which is not a function');
    }

    // jshint validthis: true
    return obj.apply(context, args);
}

function contextOrFrameLookup(context, frame, name) {
    var val = frame.lookup(name);
    return (val !== undefined) ?
        val :
        context.lookup(name);
}

function handleError(error, lineno, colno) {
    if(error.lineno) {
        return error;
    }
    else {
        return new lib.TemplateError(error, lineno, colno);
    }
}

function asyncEach(arr, dimen, iter, cb) {
    if(lib.isArray(arr)) {
        var len = arr.length;

        lib.asyncIter(arr, function(item, i, next) {
            switch(dimen) {
            case 1: iter(item, i, len, next); break;
            case 2: iter(item[0], item[1], i, len, next); break;
            case 3: iter(item[0], item[1], item[2], i, len, next); break;
            default:
                item.push(i, next);
                iter.apply(this, item);
            }
        }, cb);
    }
    else {
        lib.asyncFor(arr, function(key, val, i, len, next) {
            iter(key, val, i, len, next);
        }, cb);
    }
}

function asyncAll(arr, dimen, func, cb) {
    var finished = 0;
    var len, i;
    var outputArr;

    function done(i, output) {
        finished++;
        outputArr[i] = output;

        if(finished === len) {
            cb(null, outputArr.join(''));
        }
    }

    if(lib.isArray(arr)) {
        len = arr.length;
        outputArr = new Array(len);

        if(len === 0) {
            cb(null, '');
        }
        else {
            for(i = 0; i < arr.length; i++) {
                var item = arr[i];

                switch(dimen) {
                case 1: func(item, i, len, done); break;
                case 2: func(item[0], item[1], i, len, done); break;
                case 3: func(item[0], item[1], item[2], i, len, done); break;
                default:
                    item.push(i, done);
                    // jshint validthis: true
                    func.apply(this, item);
                }
            }
        }
    }
    else {
        var keys = lib.keys(arr);
        len = keys.length;
        outputArr = new Array(len);

        if(len === 0) {
            cb(null, '');
        }
        else {
            for(i = 0; i < keys.length; i++) {
                var k = keys[i];
                func(k, arr[k], i, len, done);
            }
        }
    }
}

module.exports = {
    Frame: Frame,
    makeMacro: makeMacro,
    makeKeywordArgs: makeKeywordArgs,
    numArgs: numArgs,
    suppressValue: suppressValue,
    ensureDefined: ensureDefined,
    memberLookup: memberLookup,
    contextOrFrameLookup: contextOrFrameLookup,
    callWrap: callWrap,
    handleError: handleError,
    isArray: lib.isArray,
    keys: lib.keys,
    SafeString: SafeString,
    copySafeness: copySafeness,
    markSafe: markSafe,
    asyncEach: asyncEach,
    asyncAll: asyncAll,
    inOperator: lib.inOperator
};
<commit_msg>fix autoescape for array values; fixes #835<commit_after>'use strict';

var lib = require('./lib');
var Obj = require('./object');

// Frames keep track of scoping both at compile-time and run-time so
// we know how to access variables. Block tags can introduce special
// variables, for example.
var Frame = Obj.extend({
    init: function(parent, isolateWrites) {
        this.variables = {};
        this.parent = parent;
        this.topLevel = false;
        // if this is true, writes (set) should never propagate upwards past
        // this frame to its parent (though reads may).
        this.isolateWrites = isolateWrites;
    },

    set: function(name, val, resolveUp) {
        // Allow variables with dots by automatically creating the
        // nested structure
        var parts = name.split('.');
        var obj = this.variables;
        var frame = this;

        if(resolveUp) {
            if((frame = this.resolve(parts[0], true))) {
                frame.set(name, val);
                return;
            }
        }

        for(var i=0; i<parts.length - 1; i++) {
            var id = parts[i];

            if(!obj[id]) {
                obj[id] = {};
            }
            obj = obj[id];
        }

        obj[parts[parts.length - 1]] = val;
    },

    get: function(name) {
        var val = this.variables[name];
        if(val !== undefined) {
            return val;
        }
        return null;
    },

    lookup: function(name) {
        var p = this.parent;
        var val = this.variables[name];
        if(val !== undefined) {
            return val;
        }
        return p && p.lookup(name);
    },

    resolve: function(name, forWrite) {
        var p = (forWrite && this.isolateWrites) ? undefined : this.parent;
        var val = this.variables[name];
        if(val !== undefined) {
            return this;
        }
        return p && p.resolve(name);
    },

    push: function(isolateWrites) {
        return new Frame(this, isolateWrites);
    },

    pop: function() {
        return this.parent;
    }
});

function makeMacro(argNames, kwargNames, func) {
    return function() {
        var argCount = numArgs(arguments);
        var args;
        var kwargs = getKeywordArgs(arguments);
        var i;

        if(argCount > argNames.length) {
            args = Array.prototype.slice.call(arguments, 0, argNames.length);

            // Positional arguments that should be passed in as
            // keyword arguments (essentially default values)
            var vals = Array.prototype.slice.call(arguments, args.length, argCount);
            for(i = 0; i < vals.length; i++) {
                if(i < kwargNames.length) {
                    kwargs[kwargNames[i]] = vals[i];
                }
            }

            args.push(kwargs);
        }
        else if(argCount < argNames.length) {
            args = Array.prototype.slice.call(arguments, 0, argCount);

            for(i = argCount; i < argNames.length; i++) {
                var arg = argNames[i];

                // Keyword arguments that should be passed as
                // positional arguments, i.e. the caller explicitly
                // used the name of a positional arg
                args.push(kwargs[arg]);
                delete kwargs[arg];
            }

            args.push(kwargs);
        }
        else {
            args = arguments;
        }

        return func.apply(this, args);
    };
}

function makeKeywordArgs(obj) {
    obj.__keywords = true;
    return obj;
}

function getKeywordArgs(args) {
    var len = args.length;
    if(len) {
        var lastArg = args[len - 1];
        if(lastArg && lastArg.hasOwnProperty('__keywords')) {
            return lastArg;
        }
    }
    return {};
}

function numArgs(args) {
    var len = args.length;
    if(len === 0) {
        return 0;
    }

    var lastArg = args[len - 1];
    if(lastArg && lastArg.hasOwnProperty('__keywords')) {
        return len - 1;
    }
    else {
        return len;
    }
}

// A SafeString object indicates that the string should not be
// autoescaped. This happens magically because autoescaping only
// occurs on primitive string objects.
function SafeString(val) {
    if(typeof val !== 'string') {
        return val;
    }

    this.val = val;
    this.length = val.length;
}

SafeString.prototype = Object.create(String.prototype, {
    length: { writable: true, configurable: true, value: 0 }
});
SafeString.prototype.valueOf = function() {
    return this.val;
};
SafeString.prototype.toString = function() {
    return this.val;
};

function copySafeness(dest, target) {
    if(dest instanceof SafeString) {
        return new SafeString(target);
    }
    return target.toString();
}

function markSafe(val) {
    var type = typeof val;

    if(type === 'string') {
        return new SafeString(val);
    }
    else if(type !== 'function') {
        return val;
    }
    else {
        return function() {
            var ret = val.apply(this, arguments);

            if(typeof ret === 'string') {
                return new SafeString(ret);
            }

            return ret;
        };
    }
}

function suppressValue(val, autoescape) {
    val = (val !== undefined && val !== null) ? val : '';

    if(autoescape && !(val instanceof SafeString)) {
        val = lib.escape(''+val);
    }

    return val;
}

function ensureDefined(val, lineno, colno) {
    if(val === null || val === undefined) {
        throw new lib.TemplateError(
            'attempted to output null or undefined value',
            lineno + 1,
            colno + 1
        );
    }
    return val;
}

function memberLookup(obj, val) {
    obj = obj || {};

    if(typeof obj[val] === 'function') {
        return function() {
            return obj[val].apply(obj, arguments);
        };
    }

    return obj[val];
}

function callWrap(obj, name, context, args) {
    if(!obj) {
        throw new Error('Unable to call `' + name + '`, which is undefined or falsey');
    }
    else if(typeof obj !== 'function') {
        throw new Error('Unable to call `' + name + '`, which is not a function');
    }

    // jshint validthis: true
    return obj.apply(context, args);
}

function contextOrFrameLookup(context, frame, name) {
    var val = frame.lookup(name);
    return (val !== undefined) ?
        val :
        context.lookup(name);
}

function handleError(error, lineno, colno) {
    if(error.lineno) {
        return error;
    }
    else {
        return new lib.TemplateError(error, lineno, colno);
    }
}

function asyncEach(arr, dimen, iter, cb) {
    if(lib.isArray(arr)) {
        var len = arr.length;

        lib.asyncIter(arr, function(item, i, next) {
            switch(dimen) {
            case 1: iter(item, i, len, next); break;
            case 2: iter(item[0], item[1], i, len, next); break;
            case 3: iter(item[0], item[1], item[2], i, len, next); break;
            default:
                item.push(i, next);
                iter.apply(this, item);
            }
        }, cb);
    }
    else {
        lib.asyncFor(arr, function(key, val, i, len, next) {
            iter(key, val, i, len, next);
        }, cb);
    }
}

function asyncAll(arr, dimen, func, cb) {
    var finished = 0;
    var len, i;
    var outputArr;

    function done(i, output) {
        finished++;
        outputArr[i] = output;

        if(finished === len) {
            cb(null, outputArr.join(''));
        }
    }

    if(lib.isArray(arr)) {
        len = arr.length;
        outputArr = new Array(len);

        if(len === 0) {
            cb(null, '');
        }
        else {
            for(i = 0; i < arr.length; i++) {
                var item = arr[i];

                switch(dimen) {
                case 1: func(item, i, len, done); break;
                case 2: func(item[0], item[1], i, len, done); break;
                case 3: func(item[0], item[1], item[2], i, len, done); break;
                default:
                    item.push(i, done);
                    // jshint validthis: true
                    func.apply(this, item);
                }
            }
        }
    }
    else {
        var keys = lib.keys(arr);
        len = keys.length;
        outputArr = new Array(len);

        if(len === 0) {
            cb(null, '');
        }
        else {
            for(i = 0; i < keys.length; i++) {
                var k = keys[i];
                func(k, arr[k], i, len, done);
            }
        }
    }
}

module.exports = {
    Frame: Frame,
    makeMacro: makeMacro,
    makeKeywordArgs: makeKeywordArgs,
    numArgs: numArgs,
    suppressValue: suppressValue,
    ensureDefined: ensureDefined,
    memberLookup: memberLookup,
    contextOrFrameLookup: contextOrFrameLookup,
    callWrap: callWrap,
    handleError: handleError,
    isArray: lib.isArray,
    keys: lib.keys,
    SafeString: SafeString,
    copySafeness: copySafeness,
    markSafe: markSafe,
    asyncEach: asyncEach,
    asyncAll: asyncAll,
    inOperator: lib.inOperator
};
<|endoftext|>


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

module.exports = function (purchaseRequest, offset) {

    var items = [].concat.apply([], purchaseRequest.items);

    var iso = "";
    var number = purchaseRequest.no;

    var locale = global.config.locale;

    var moment = require('moment');
    moment.locale(locale.name);

    var header = [{
        columns: [{
            columns: [{
                width: '*',
                stack: [{
                    text: 'PT EFRATA RETAILINDO',
                    style: ['size15'],
                    alignment: "center"
                }, {
                    text: 'BANARAN, GROGOL, SUKOHARJO',
                    style: ['size09'],
                    alignment: "center"
                }, {
                    text: '57193, Jawa Tengah, Indonesia ',
                    style: ['size06'],
                    alignment: "center"
                }]
            }]

        }]
    }];

    var line = [{
        canvas: [{
            type: 'line',
            x1: 0,
            y1: 5,
            x2: 378,
            y2: 5,
            lineWidth: 0.5
        }
        ]
    }, '\n'];

    var subheader = [{
        columns: [{
            columns: [{
                width: '*',
                stack: [{
                    text: iso,
                    style: ['size09', 'bold'],
                    alignment: "right"
                },
                {
                    text: 'ORDER PEMESANAN',
                    style: ['size09', 'bold'],
                    alignment: "center",
                    decoration: 'underline'
                }]
            }]

        }]
    }];

    var subheader2 = [{
        columns: [
            {
                width: '50%',
                columns: [{
                    width: '25%',
                    stack: ['Bagian', 'Budget', 'Nomor']
                }, {
                    width: '5%',
                    stack: [':', ':', ':']
                }, {
                    width: '*',
                    stack: [purchaseRequest.unit.name, purchaseRequest.budget.name, purchaseRequest.no]
                }],
                style: ['size08']

            },
            {
                width: '20%',
                text: ''
            },
            {
                width: '30%',
                columns: [{
                    width: '*',
                    stack: [`Sukoharjo, ${moment(purchaseRequest.date).add(offset, 'h').format(locale.date.format)} `],
                    alignment: "right"
                }],
                style: ['size08']
            }
        ]
    }];

    var opening = {
        text: [
            '\n', {
                text: 'Mohon dibelikan/diusahakan barang tersebut dibawah ini : '
            },
            '\n\n'
        ],
        style: ['size09']
    };

    var thead = [{
        text: 'NO',
        style: 'tableHeader'
    }, {
        text: 'KODE',
        style: 'tableHeader'
    }, {
        text: 'BARANG',
        style: 'tableHeader'
    }, {
        text: 'JUMLAH',
        style: 'tableHeader'
    }, {
        text: 'HARGA',
        style: 'tableHeader'
    }];

    var tbody = items.map(function (item, index) {
        return [{
            text: (index + 1).toString() || '',
            style: ['size08', 'center']
        }, {
            text: item.product.code,
            style: ['size08', 'left']
        }, {
            text: item.product.name + '\n' + item.remark,
            style: ['size08', 'left']
        }, {
            text: parseInt(item.quantity).toLocaleString(locale, locale.parseInt) + " " + item.product.uom.unit,
            style: ['size08', 'center']
        }, {
            text: '',
            style: ['size08', 'left']
        }];
    });

    var tfoot = [[{
        text: " ",
        style: ['size08', 'center']
    }, "", "", "", ""]];

    tbody = tbody.length > 0 ? tbody : [
        [{
            text: "tidak ada barang",
            style: ['size08', 'center'],
            colSpan: 5
        }, "", "", "", ""]
    ];

    var table = [{
        table: {
            widths: ['5%', '19%', '39%', '17%', '20%'],
            headerRows: 1,
            body: [].concat([thead], tbody, tfoot)
        }
    }];

    var getDateexpected = purchaseRequest.expectedDeliveryDate && purchaseRequest.expectedDeliveryDate.toString().trim() != '' ? moment(purchaseRequest.expectedDeliveryDate).add(offset, 'h').format(locale.date.format) : '-';

    var footer = [
        '\n', {
            stack: [{
                columns: [{
                    width: '80%',
                    columns: [{
                        width: '25%',
                        stack: ['Kategori', 'Diminta Datang', 'Keterangan']
                    }, {
                        width: '3%',
                        stack: [':', ':', ':']
                    }, {
                        width: '*',
                        stack: [purchaseRequest.category.name, `${getDateexpected}`, purchaseRequest.remark]
                    }]
                }]
            }
            ],
            style: ['size08']
        }, '\n'
    ];

    var thead2 = [{
        text: 'YANG MEMERLUKAN',
        style: 'tableHeader'
    }, {
        text: 'KEPALA BAGIAN',
        style: 'tableHeader'
    }, {
        text: 'BAGIAN PEMBELIAN',
        style: 'tableHeader'
    }];

    var tbody2 = [[{
        text: " ",
        style: ['size30', 'center']
    }, "", ""]];

    var table2 = [{
        table: {
            widths: ['30%', '30%', '30%'],
            headerRows: 1,
            body: [].concat([thead2], tbody2)
        }
    }];

    var pr = {
        pageSize: 'A5',
        pageOrientation: 'portrait',
        pageMargins: 20,
        content: [].concat(header, line, subheader, subheader2, opening, table, footer, table2),
        styles: {
            size06: {
                fontSize: 6
            },
            size07: {
                fontSize: 7
            },
            size08: {
                fontSize: 8
            },
            size09: {
                fontSize: 9
            },
            size10: {
                fontSize: 10
            },
            size15: {
                fontSize: 15
            },
            size30: {
                fontSize: 30
            },
            bold: {
                bold: true
            },
            center: {
                alignment: 'center'
            },
            left: {
                alignment: 'left'
            },
            right: {
                alignment: 'right'
            },
            justify: {
                alignment: 'justify'
            },
            tableHeader: {
                bold: true,
                fontSize: 8,
                color: 'black',
                alignment: 'center'
            }
        }
    };

    return pr;
}<commit_msg>resize wiidth for pdf<commit_after>var global = require('../../global');

module.exports = function (purchaseRequest, offset) {

    var items = [].concat.apply([], purchaseRequest.items);

    var iso = "";
    var number = purchaseRequest.no;

    var locale = global.config.locale;

    var moment = require('moment');
    moment.locale(locale.name);

    var header = [{
        columns: [{
            columns: [{
                width: '*',
                stack: [{
                    text: 'PT EFRATA RETAILINDO',
                    style: ['size15'],
                    alignment: "center"
                }, {
                    text: 'BANARAN, GROGOL, SUKOHARJO',
                    style: ['size09'],
                    alignment: "center"
                }, {
                    text: '57193, Jawa Tengah, Indonesia ',
                    style: ['size06'],
                    alignment: "center"
                }]
            }]

        }]
    }];

    var line = [{
        canvas: [{
            type: 'line',
            x1: 0,
            y1: 5,
            x2: 378,
            y2: 5,
            lineWidth: 0.5
        }
        ]
    }, '\n'];

    var subheader = [{
        columns: [{
            columns: [{
                width: '*',
                stack: [{
                    text: iso,
                    style: ['size09', 'bold'],
                    alignment: "right"
                },
                {
                    text: 'ORDER PEMESANAN',
                    style: ['size09', 'bold'],
                    alignment: "center",
                    decoration: 'underline'
                }]
            }]

        }]
    }];

    var subheader2 = [{
        columns: [
            {
                width: '50%',
                columns: [{
                    width: '25%',
                    stack: ['Bagian', 'Budget', 'Nomor']
                }, {
                    width: '5%',
                    stack: [':', ':', ':']
                }, {
                    width: '*',
                    stack: [purchaseRequest.unit.name, purchaseRequest.budget.name, purchaseRequest.no]
                }],
                style: ['size08']

            },
            {
                width: '20%',
                text: ''
            },
            {
                width: '30%',
                columns: [{
                    width: '*',
                    stack: [`Sukoharjo, ${moment(purchaseRequest.date).add(offset, 'h').format(locale.date.format)} `],
                    alignment: "right"
                }],
                style: ['size08']
            }
        ]
    }];

    var opening = {
        text: [
            '\n', {
                text: 'Mohon dibelikan/diusahakan barang tersebut dibawah ini : '
            },
            '\n\n'
        ],
        style: ['size09']
    };

    var thead = [{
        text: 'NO',
        style: 'tableHeader'
    }, {
        text: 'KODE',
        style: 'tableHeader'
    }, {
        text: 'BARANG',
        style: 'tableHeader'
    }, {
        text: 'JUMLAH',
        style: 'tableHeader'
    }, {
        text: 'HARGA',
        style: 'tableHeader'
    }];

    var tbody = items.map(function (item, index) {
        return [{
            text: (index + 1).toString() || '',
            style: ['size08', 'center']
        }, {
            text: item.product.code,
            style: ['size08', 'left']
        }, {
            text: item.product.name + '\n' + item.remark,
            style: ['size08', 'left']
        }, {
            text: parseInt(item.quantity).toLocaleString(locale, locale.parseInt) + " " + item.product.uom.unit,
            style: ['size08', 'center']
        }, {
            text: '',
            style: ['size08', 'left']
        }];
    });

    var tfoot = [[{
        text: " ",
        style: ['size08', 'center']
    }, "", "", "", ""]];

    tbody = tbody.length > 0 ? tbody : [
        [{
            text: "tidak ada barang",
            style: ['size08', 'center'],
            colSpan: 5
        }, "", "", "", ""]
    ];

    var table = [{
        table: {
            widths: ['5%', '19%', '39%', '17%', '20%'],
            headerRows: 1,
            body: [].concat([thead], tbody, tfoot)
        }
    }];

    var getDateexpected = purchaseRequest.expectedDeliveryDate && purchaseRequest.expectedDeliveryDate.toString().trim() != '' ? moment(purchaseRequest.expectedDeliveryDate).add(offset, 'h').format(locale.date.format) : '-';

    var footer = [
        '\n', {
            stack: [{
                columns: [{
                    width: '80%',
                    columns: [{
                        width: '25%',
                        stack: ['Kategori', 'Diminta Datang', 'Keterangan']
                    }, {
                        width: '3%',
                        stack: [':', ':', ':']
                    }, {
                        width: '*',
                        stack: [purchaseRequest.category.name, `${getDateexpected}`, purchaseRequest.remark]
                    }]
                }]
            }
            ],
            style: ['size08']
        }, '\n'
    ];

    var thead2 = [{
        text: 'YANG MEMERLUKAN',
        style: 'tableHeader'
    }, {
        text: 'KEPALA BAGIAN',
        style: 'tableHeader'
    }, {
        text: 'BAGIAN PEMBELIAN',
        style: 'tableHeader'
    }];

    var tbody2 = [[{
        text: " ",
        style: ['size30', 'center']
    }, "", ""]];

    var table2 = [{
        table: {
            widths: ['33%', '33%', '33%'],
            headerRows: 1,
            body: [].concat([thead2], tbody2)
        }
    }];

    var pr = {
        pageSize: 'A5',
        pageOrientation: 'portrait',
        pageMargins: 20,
        content: [].concat(header, line, subheader, subheader2, opening, table, footer, table2),
        styles: {
            size06: {
                fontSize: 6
            },
            size07: {
                fontSize: 7
            },
            size08: {
                fontSize: 8
            },
            size09: {
                fontSize: 9
            },
            size10: {
                fontSize: 10
            },
            size15: {
                fontSize: 15
            },
            size30: {
                fontSize: 30
            },
            bold: {
                bold: true
            },
            center: {
                alignment: 'center'
            },
            left: {
                alignment: 'left'
            },
            right: {
                alignment: 'right'
            },
            justify: {
                alignment: 'justify'
            },
            tableHeader: {
                bold: true,
                fontSize: 8,
                color: 'black',
                alignment: 'center'
            }
        }
    };

    return pr;
}<|endoftext|>


<commit_before>(function($) {
$.fn.bkviewer_rounded_box = function(options) {
return this.each(function() {
	var t = $(this);
	var hiddens = new Object();

	var get_pref = $().get_pref;
	var save_pref = $().save_pref;

	function remove_item(hiddens, i) {
		var items = hiddens.items, head, tail;

		head = items.slice(0, i);
		tail = items.slice(i+1, items.length);
		hiddens.items = head.concat(tail);
	}

	function update_hiddens(box, title, hiddens) {
		var i = hiddens.items.indexOf(title);
		var pref_set = {};

		if (box.css("display") == "none") {
			if (i == -1)
				hiddens.items.push(title);
		} else {
			if (i != -1)
				remove_item(hiddens, i);
		}

		save_pref("hiddens", JSON.stringify(hiddens.items), pref_set);
	}

	function toggle(box, hiddens) {
		var title = box.prev();
		if (box.css("display") != "none") {
			title.animate({"border-bottom-left-radius": 8,
				       "border-bottom-right-radius": 8}, 400);
		} else {
			title.css("border-radius", "8px 8px 0px 0px");
		}
		box.slideToggle(function() {
			var ul = $(this);
			update_hiddens(ul, ul.prev().text(), hiddens);
		});
	}

	function hide_categories(target, hiddens) {
		/* Users may change titles, so this synchronize
		 * the preferences with the actual titles.
		 */
		var new_hiddens = [];
		var i, box, title;
		var pref_set = {};

		target.children().each(function() {
			box = $(this);
			title = box.children("li").text();
			i = hiddens.items.indexOf(title);
			if (i != -1) {
				box.children("li").css("border-radius", "8px");
				box.children("ul").hide();
				new_hiddens.push(title);
			}
		});

		hiddens.items = new_hiddens;
		save_pref("hiddens", JSON.stringify(hiddens.items), pref_set);
	}

	function align_width(contents) {
		var item_width = 0;

		contents.width("")
			.each(function() {
			if (item_width < $(this).width()) {
				item_width = $(this).width();
			}
		});

		contents.width(item_width + 1);
	}

	function dress(pref_set) {
		var json;
		var font_size = String(pref_set.zoom) + "%";
		var favicon_size = String(Math.floor(
						12 * pref_set.zoom / 100.0)
					 ) + "px";

		json = {
			"body": {
				"background-color": pref_set.bg_color,
				"color": pref_set.text_color,
				"font-family": "\"Helvetica Neue\",Helvetica,Arial,sans-serif",
				"font-size": font_size,
			},

			"ul.bk-category": {
				"padding-top":   "0px",
				"padding-left":  "0px",
				"padding-right": "0px",
				"margin-left":   "0px",
				"margin-right":  "0px",
				"border-color":  pref_set.frame_color,
				"border-width":  "1px",
				"border-style":  "solid",
				"border-radius": "10px",
			},

			"li.bk-category": {
				"color":	    pref_set.bg_color,
				"background-color": pref_set.frame_color,
				"text-align":       "center",
				"font-weight":      "bold",
				"list-style-type":  "none",
				"margin-top":       "0px",
				"border-radius":    "8px 8px 0px 0px",
			},
			"li.bk-category:hover": {
				"cursor": "pointer",
			},

			"ul.bk-item": {
				"padding":       "3px 0px",
				"margin-left":   "0px",
				"margin-right":  "0px",
			},

			"li.bk-item": {
				"list-style-type": "none",
				"margin-top":      "2px",
				"margin-bottom":   "2px",
				"padding-left":    "10px",
				"padding-right":   "10px",
			},

			"a.bk-item, a.configuration": {
				"text-decoration": "none",
				"color": pref_set.text_color,
			},

			"img.favicon": {
				"width":   favicon_size,
				"height":  favicon_size,
				"margin":  "0 5px 0 0",
				"padding": "0px 0px 0px 0px",
			},

			"p.configuration": {
				"text-align": "right",
			},

			".tclock": {
				"text-align": "center",
				"color":      pref_set.bg_color,
				"margin-top":  "12px",
			},

			".tclock-time": {
				"font-size": "220%",
			},

			".tclock-date": {
				"font-size": "110%",
			},
		};

		return json;
	}

	t.show_bookmarks({
		add_category_view: function(json, target, pref_set, context) {
			var result, box = target;

			box = $("<ul />").addClass("bk-category")
					 .appendTo(box);

			$("<li />").text(json["title"])
				   .addClass("bk-category")
				   .appendTo(box)
				   .click(function() {
					toggle($(this).next(), hiddens);
				   });

			box = $("<ul />").addClass("bk-item")
					 .appendTo(box);

			context.cur_box = box;
		},

		add_item_view: function(json, target, pref_set, context) {
			var box = context.cur_box, atag, img;
			var default_height;

			box = $("<li />").addClass("bk-item")
					 .appendTo(box);

			atag = $("<a />").text(json["title"])
				  .attr({"href": json["uri"]})
				  .addClass("bk-item")
				  .appendTo(box);

			img = $("<img />").addClass("favicon")
				    .attr({"src": json["favicon"]})
				    .prependTo(atag);

			return img;
		},

		fini: function(target, pref_set, context) {
			target.children().children("ul").addClass("bv-clearfix")
						   .each(function() {
				var ul = $(this);
				align_width(ul.children());
			});

			hiddens.items = JSON.parse(pref_set.hiddens);
			hide_categories(target, hiddens);
		},

		pref_set: {
			"hiddens": "[]",
			"frame_color":	"#00aacc",
			"bg_color":	"#ffffff",
			"text_color":	"#0000ff",
			"zoom":		100
		},

		dress: dress
	});

})}}) (jQuery);
<commit_msg>Rounded box: Introduces CSS3 flex style<commit_after>(function($) {
$.fn.bkviewer_rounded_box = function(options) {
return this.each(function() {
	var t = $(this);
	var hiddens = new Object();

	var get_pref = $().get_pref;
	var save_pref = $().save_pref;

	function remove_item(hiddens, i) {
		var items = hiddens.items, head, tail;

		head = items.slice(0, i);
		tail = items.slice(i+1, items.length);
		hiddens.items = head.concat(tail);
	}

	function update_hiddens(box, title, hiddens) {
		var i = hiddens.items.indexOf(title);
		var pref_set = {};

		if (box.css("display") == "none") {
			if (i == -1)
				hiddens.items.push(title);
		} else {
			if (i != -1)
				remove_item(hiddens, i);
		}

		save_pref("hiddens", JSON.stringify(hiddens.items), pref_set);
	}

	function toggle(box, hiddens) {
		var title = box.prev();
		if (box.css("display") != "none") {
			title.animate({"border-bottom-left-radius": 8,
				       "border-bottom-right-radius": 8}, 400);
		} else {
			title.css("border-radius", "8px 8px 0px 0px");
		}
		box.slideToggle(function() {
			var ul = $(this);
			update_hiddens(ul, ul.prev().text(), hiddens);
		});
	}

	function hide_categories(target, hiddens) {
		/* Users may change titles, so this synchronize
		 * the preferences with the actual titles.
		 */
		var new_hiddens = [];
		var i, box, title;
		var pref_set = {};

		target.children().each(function() {
			box = $(this);
			title = box.children("li").text();
			i = hiddens.items.indexOf(title);
			if (i != -1) {
				box.children("li").css("border-radius", "8px");
				box.children("ul").hide();
				new_hiddens.push(title);
			}
		});

		hiddens.items = new_hiddens;
		save_pref("hiddens", JSON.stringify(hiddens.items), pref_set);
	}

	function align_width(contents) {
		var item_width = 0;

		contents.width("")
			.each(function() {
			if (item_width < $(this).width()) {
				item_width = $(this).width();
			}
		});

		contents.width(item_width + 1);
	}

	function dress(pref_set) {
		var json;
		var font_size = String(pref_set.zoom) + "%";
		var favicon_size = String(Math.floor(
						12 * pref_set.zoom / 100.0)
					 ) + "px";

		json = {
			"body": {
				"background-color": pref_set.bg_color,
				"color": pref_set.text_color,
				"font-family": "\"Helvetica Neue\",Helvetica,Arial,sans-serif",
				"font-size": font_size,
			},

			"ul.bk-category": {
				"padding-top":   "0px",
				"padding-left":  "0px",
				"padding-right": "0px",
				"margin-left":   "0px",
				"margin-right":  "0px",
				"border-color":  pref_set.frame_color,
				"border-width":  "1px",
				"border-style":  "solid",
				"border-radius": "10px",
			},

			"li.bk-category": {
				"color":	    pref_set.bg_color,
				"background-color": pref_set.frame_color,
				"text-align":       "center",
				"font-weight":      "bold",
				"list-style-type":  "none",
				"margin-top":       "0px",
				"border-radius":    "8px 8px 0px 0px",
			},
			"li.bk-category:hover": {
				"cursor": "pointer",
			},

			"ul.bk-item": {
				"padding":       "3px 0px",
				"margin-left":   "0px",
				"margin-right":  "0px",
				"display":	 "flex",
				"flex-wrap":	 "wrap",
				"justify-content": "space-between",
			},

			"li.bk-item": {
				"list-style-type": "none",
				"margin-top":      "2px",
				"margin-bottom":   "2px",
				"padding-left":    "10px",
				"padding-right":   "10px",
			},

			"a.bk-item, a.configuration": {
				"text-decoration": "none",
				"color": pref_set.text_color,
			},

			"img.favicon": {
				"width":   favicon_size,
				"height":  favicon_size,
				"margin":  "0 5px 0 0",
				"padding": "0px 0px 0px 0px",
			},

			"p.configuration": {
				"text-align": "right",
			},

			".tclock": {
				"text-align": "center",
				"color":      pref_set.bg_color,
				"margin-top":  "12px",
			},

			".tclock-time": {
				"font-size": "220%",
			},

			".tclock-date": {
				"font-size": "110%",
			},
		};

		return json;
	}

	t.show_bookmarks({
		add_category_view: function(json, target, pref_set, context) {
			var result, box = target;

			box = $("<ul />").addClass("bk-category")
					 .appendTo(box);

			$("<li />").text(json["title"])
				   .addClass("bk-category")
				   .appendTo(box)
				   .click(function() {
					toggle($(this).next(), hiddens);
				   });

			box = $("<ul />").addClass("bk-item")
					 .appendTo(box);

			context.cur_box = box;
		},

		add_item_view: function(json, target, pref_set, context) {
			var box = context.cur_box, atag, img;
			var default_height;

			box = $("<li />").addClass("bk-item")
					 .appendTo(box);

			atag = $("<a />").text(json["title"])
				  .attr({"href": json["uri"]})
				  .addClass("bk-item")
				  .appendTo(box);

			img = $("<img />").addClass("favicon")
				    .attr({"src": json["favicon"]})
				    .prependTo(atag);

			return img;
		},

		fini: function(target, pref_set, context) {
			target.children().children("ul").addClass("bv-clearfix")
						   .each(function() {
				var ul = $(this);
				align_width(ul.children());
			});

			hiddens.items = JSON.parse(pref_set.hiddens);
			hide_categories(target, hiddens);
		},

		pref_set: {
			"hiddens": "[]",
			"frame_color":	"#00aacc",
			"bg_color":	"#ffffff",
			"text_color":	"#0000ff",
			"zoom":		100
		},

		dress: dress
	});

})}}) (jQuery);
<|endoftext|>