<commit_before>const CoreApp = require('kolibri');
const pick = require('lodash/fp/pick');
const ConditionalPromise = require('kolibri.lib.conditionalPromise');
const router = require('kolibri.coreVue.router');
const CoreActions = require('kolibri.coreVue.vuex.actions');
const ContentNodeKinds = require('kolibri.coreVue.vuex.constants').ContentNodeKinds;
const CollectionKinds = require('kolibri.coreVue.vuex.constants').CollectionKinds;
const Constants = require('../../constants');

const ClassroomResource = CoreApp.resources.ClassroomResource;
const ChannelResource = CoreApp.resources.ChannelResource;
const LearnerGroupResource = CoreApp.resources.LearnerGroupResource;
const ContentNodeResource = CoreApp.resources.ContentNodeResource;
const ExamResource = CoreApp.resources.ExamResource;
const ExamAssignmentResource = CoreApp.resources.ExamAssignmentResource;

const pickIdAndName = pick(['id', 'name']);

function _channelState(channel) {
  return {
    id: channel.id,
    name: channel.name,
    rootPk: channel.root_pk,
  };
}

function _channelsState(channels) {
  return channels.map(channel => _channelState(channel));
}

function _breadcrumbState(topic) {
  return {
    id: topic.pk,
    title: topic.title,
  };
}

function _breadcrumbsState(topics) {
  return topics.map(topic => _breadcrumbState(topic));
}

function _currentTopicState(topic) {
  let breadcrumbs = Array.from(topic.ancestors);
  breadcrumbs.push({ pk: topic.pk, title: topic.title });
  breadcrumbs = _breadcrumbsState(breadcrumbs);
  return {
    id: topic.pk,
    title: topic.title,
    breadcrumbs,
  };
}

function _topicState(topic) {
  return {
    id: topic.pk,
    title: topic.title,
  };
}

function _topicsState(topics) {
  return topics.map(topic => _topicState(topic));
}

function _exerciseState(exercise) {
  return {
    id: exercise.pk,
    title: exercise.title,
  };
}

function _exercisesState(exercises) {
  return exercises.map(exercise => _exerciseState(exercise));
}

function _assignmentState(assignment) {
  return {
    assignmentId: String(assignment.id),
    collection: {
      id: String(assignment.collection.id),
      name: assignment.collection.name,
      kind: assignment.collection.kind,
    },
    examId: String(assignment.exam),
  };
}

function _assignmentsState(assignments) {
  return assignments.map(assignment => _assignmentState(assignment));
}

function _examState(exam) {
  const assignments = _assignmentsState(exam.assignments);
  const visibility = {};
  visibility.class = assignments.find(
    assignment => assignment.collection.kind === CollectionKinds.CLASSROOM);
  visibility.groups = assignments.filter(
    assignment => assignment.collection.kind === CollectionKinds.LEARNERGROUP);
  return {
    id: exam.id,
    title: exam.title,
    channelId: exam.channel_id,
    collection: exam.collection,
    active: exam.active,
    archive: exam.archive,
    questionCount: exam.question_count,
    questionSources: exam.question_sources,
    seed: exam.seed,
    visibility,
  };
}

function _examsState(exams, classId) {
  return exams.map(exam => _examState(exam, classId));
}

function displayExamModal(store, modalName) {
  store.dispatch('SET_EXAM_MODAL', modalName);
}

function showExamsPage(store, classId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.EXAMS);

  const resourceRequests = [
    ClassroomResource.getModel(classId).fetch(),
    LearnerGroupResource.getCollection({ parent: classId }).fetch(),
    ChannelResource.getCollection().fetch(),
    ExamResource.getCollection({ collection: classId }).fetch({}, true),
  ];

  return ConditionalPromise.all(resourceRequests).only(
    CoreActions.samePageCheckGenerator(store),
    ([classroom, learnerGroups, channels, exams]) => {
      const pageState = {
        channels: _channelsState(channels),
        classId,
        currentClass: pickIdAndName(classroom),
        currentClassGroups: learnerGroups.map(pickIdAndName),
        exams: _examsState(exams, classId),
        examModalShown: false,
      };

      store.dispatch('SET_PAGE_STATE', pageState);
      store.dispatch('CORE_SET_ERROR', null);
      store.dispatch('CORE_SET_TITLE', Constants.PageTitles.EXAMS);
      store.dispatch('CORE_SET_PAGE_LOADING', false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function activateExam(store, examId) {
  ExamResource.getModel(examId).save({ active: true }).then(
    () => {
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      exams[examIndex].active = true;

      store.dispatch('SET_EXAMS', exams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function deactivateExam(store, examId) {
  ExamResource.getModel(examId).save({ active: false }).then(
    () => {
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      exams[examIndex].active = false;

      store.dispatch('SET_EXAMS', exams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function _assignExamTo(examId, collection) {
  const assignmentPayload = {
    exam: examId,
    collection,
  };
  return new Promise((resolve, reject) => {
    ExamAssignmentResource.createModel(assignmentPayload).save().then(
      assignment => resolve(assignment),
      error => reject(error)
    );
  });
}

function _removeAssignment(assignmentId) {
  return new Promise((resolve, reject) => {
    ExamAssignmentResource.getModel(assignmentId).delete().then(
      () => resolve(),
      error => reject(error)
    );
  });
}

function updateExamAssignments(store, examId, collectionsToAssign, assignmentsToRemove) {
  const assignPromises = collectionsToAssign.map(collection => _assignExamTo(examId, collection));
  const unassignPromises = assignmentsToRemove.map(assignment => _removeAssignment(assignment));
  const assignmentPromises = assignPromises.concat(unassignPromises);

  ConditionalPromise.all(assignmentPromises).only(CoreActions.samePageCheckGenerator(store),
    response => {
      let newAssignments = response.filter(n => n);
      newAssignments = _assignmentsState(newAssignments);

      const classId = store.state.pageState.currentClass.id;
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      const examVisibility = exams[examIndex].visibility;

      newAssignments.forEach(assignment => {
        if (assignment.collection.id === classId) {
          examVisibility.class = assignment;
        } else {
          examVisibility.groups.push(assignment);
        }
      });

      assignmentsToRemove.forEach(assignmentId => {
        if (examVisibility.class) {
          if (assignmentId === examVisibility.class.assignmentId) {
            examVisibility.class = null;
            return;
          }
        }
        examVisibility.groups = examVisibility.groups.filter(
          group => group.assignmentId !== assignmentId);
      });

      exams[examIndex].visibility = examVisibility;
      store.dispatch('SET_EXAMS', exams);
      store.dispatch('CORE_SET_ERROR', null);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function previewExam(store) {
  displayExamModal(store, false);
}

function renameExam(store, examId, newExamTitle) {
  ExamResource.getModel(examId).save({ title: newExamTitle }).then(
    () => {
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      exams[examIndex].title = newExamTitle;

      store.dispatch('SET_EXAMS', exams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function deleteExam(store, examId) {
  ExamResource.getModel(examId).delete().then(
    () => {
      const exams = store.state.pageState.exams;
      const updatedExams = exams.filter(exam => exam.id !== examId);

      store.dispatch('SET_EXAMS', updatedExams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function getAllExercisesWithinTopic(store, channelId, topicId) {
  return new Promise((resolve, reject) => {
    const exercisesPromise = ContentNodeResource.getDescendantsCollection(
      topicId,
      { channel_id: channelId },
      { descendant_kind: ContentNodeKinds.EXERCISE, fields: ['pk'] }
    ).fetch();

    ConditionalPromise.all([exercisesPromise]).only(
      CoreActions.samePageCheckGenerator(store),
      ([exercisesCollection]) => {
        const exercises = _exercisesState(exercisesCollection);
        const exerciseIds = exercises.map(exercise => exercise.id);
        resolve(exerciseIds);
      },
      error => reject(error)
    );
  });
}

// fetches topic, it's children subtopics, and children exercises
function fetchContent(store, channelId, topicId) {
  return new Promise((resolve, reject) => {
    const channelPayload = { channel_id: channelId };
    const topicPromise = ContentNodeResource.getModel(topicId, channelPayload).fetch();
    const subtopicsPromise = ContentNodeResource.getCollection(
      channelPayload, { parent: topicId, kind: ContentNodeKinds.TOPIC, fields: ['pk', 'title', 'ancestors'] }).fetch();
    const exercisesPromise = ContentNodeResource.getCollection(
      channelPayload, { parent: topicId, kind: ContentNodeKinds.EXERCISE, fields: ['pk', 'title'] }).fetch();

    ConditionalPromise.all([topicPromise, subtopicsPromise, exercisesPromise]).only(
      CoreActions.samePageCheckGenerator(store),
      ([topicModel, subtopicsCollection, exercisesCollection]) => {
        const topic = _currentTopicState(topicModel);
        const exercises = _exercisesState(exercisesCollection);
        let subtopics = _topicsState(subtopicsCollection);

        const subtopicsExercisesPromises = subtopics.map(
          subtopic => getAllExercisesWithinTopic(store, channelId, subtopic.id));

        ConditionalPromise.all(subtopicsExercisesPromises).only(
          CoreActions.samePageCheckGenerator(store),
          subtopicsExercises => {
            subtopics = subtopics.map((subtopic, index) => {
              subtopic.allExercisesWithinTopic = subtopicsExercises[index];
              return subtopic;
            });

            store.dispatch('SET_TOPIC', topic);
            store.dispatch('SET_SUBTOPICS', subtopics);
            store.dispatch('SET_EXERCISES', exercises);
            resolve({ topic, subtopics, exercises });
          },
          error => reject(error)
        );
      },
      error => reject(error)
    );
  });
}

function showCreateExamPage(store, classId, channelId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.CREATE_EXAM);
  store.dispatch('CORE_SET_TITLE', Constants.PageTitles.CREATE_EXAM);

  const currentClassPromise = ClassroomResource.getModel(classId).fetch();
  const channelPromise = ChannelResource.getCollection().fetch();

  ConditionalPromise.all([currentClassPromise, channelPromise]).only(
    CoreActions.samePageCheckGenerator(store),
    ([currentClassModel, channelsCollection]) => {
      const currentClass = pickIdAndName(currentClassModel);
      const currentChannel = _channelState(
        channelsCollection.find(channel => channel.id === channelId));

      const fetchContentPromise = fetchContent(store, channelId, currentChannel.rootPk);

      ConditionalPromise.all([fetchContentPromise]).only(
        CoreActions.samePageCheckGenerator(store),
        ([content]) => {
          const pageState = {
            currentClass,
            currentChannel,
            topic: content.topic,
            subtopics: content.subtopics,
            exercises: content.exercises,
            selectedExercises: [],
            examModalShown: false,
          };

          store.dispatch('SET_PAGE_STATE', pageState);
          store.dispatch('CORE_SET_ERROR', null);
          store.dispatch('CORE_SET_PAGE_LOADING', false);
        },
        error => CoreActions.handleError(store, error)
      );
    },
    error => CoreActions.handleError(store, error)
  );
}

function addExercise(store, exerciseId) {
  const selectedExercises = store.state.pageState.selectedExercises;
  if (!selectedExercises.includes(exerciseId)) {
    store.dispatch('SET_SELECTED_EXERCISES', selectedExercises.concat(exerciseId));
  }
}

function removeExercise(store, exerciseId) {
  const selectedExercises = store.state.pageState.selectedExercises;
  const index = selectedExercises.indexOf(exerciseId);
  if (index !== -1) {
    selectedExercises.splice(index, 1);
    store.dispatch('SET_SELECTED_EXERCISES', selectedExercises);
  }
}

function createExam(store, classCollection, examObj) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  const examPayload = {
    collection: examObj.classId,
    channel_id: examObj.channelId,
    title: examObj.title,
    question_count: examObj.numQuestions,
    question_sources: examObj.questionSources,
    seed: examObj.seed,
  };
  ExamResource.createModel(examPayload).save().then(
    exam => {
      _assignExamTo(exam.id, classCollection).then(
        () => {
          store.dispatch('CORE_SET_PAGE_LOADING', false);
          router.getInstance().push({ name: Constants.PageNames.EXAMS });
        },
        error => CoreActions.handleError(store, error)
      );
    },
    error => CoreActions.handleError(store, error)
  );
}

function showExamReportPage(store, classId, examId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.EXAM_REPORT);
  store.dispatch('SET_PAGE_STATE', {});
  store.dispatch('CORE_SET_ERROR', null);
  store.dispatch('CORE_SET_TITLE', ('Exam Report'));
  store.dispatch('CORE_SET_PAGE_LOADING', false);
}

function showExamReportDetailPage(store, classId, examId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.EXAM_REPORT_DETAIL);
  store.dispatch('SET_PAGE_STATE', {});
  store.dispatch('CORE_SET_ERROR', null);
  store.dispatch('CORE_SET_TITLE', ('Exam Report Detail'));
  store.dispatch('CORE_SET_PAGE_LOADING', false);
}

module.exports = {
  displayExamModal,
  showExamsPage,
  showCreateExamPage,
  showExamReportPage,
  showExamReportDetailPage,
  activateExam,
  deactivateExam,
  previewExam,
  renameExam,
  deleteExam,
  updateExamAssignments,
  fetchContent,
  createExam,
  addExercise,
  removeExercise,
  getAllExercisesWithinTopic,
};
<commit_msg>Stringify question sources<commit_after>const CoreApp = require('kolibri');
const pick = require('lodash/fp/pick');
const ConditionalPromise = require('kolibri.lib.conditionalPromise');
const router = require('kolibri.coreVue.router');
const CoreActions = require('kolibri.coreVue.vuex.actions');
const ContentNodeKinds = require('kolibri.coreVue.vuex.constants').ContentNodeKinds;
const CollectionKinds = require('kolibri.coreVue.vuex.constants').CollectionKinds;
const Constants = require('../../constants');

const ClassroomResource = CoreApp.resources.ClassroomResource;
const ChannelResource = CoreApp.resources.ChannelResource;
const LearnerGroupResource = CoreApp.resources.LearnerGroupResource;
const ContentNodeResource = CoreApp.resources.ContentNodeResource;
const ExamResource = CoreApp.resources.ExamResource;
const ExamAssignmentResource = CoreApp.resources.ExamAssignmentResource;

const pickIdAndName = pick(['id', 'name']);

function _channelState(channel) {
  return {
    id: channel.id,
    name: channel.name,
    rootPk: channel.root_pk,
  };
}

function _channelsState(channels) {
  return channels.map(channel => _channelState(channel));
}

function _breadcrumbState(topic) {
  return {
    id: topic.pk,
    title: topic.title,
  };
}

function _breadcrumbsState(topics) {
  return topics.map(topic => _breadcrumbState(topic));
}

function _currentTopicState(topic) {
  let breadcrumbs = Array.from(topic.ancestors);
  breadcrumbs.push({ pk: topic.pk, title: topic.title });
  breadcrumbs = _breadcrumbsState(breadcrumbs);
  return {
    id: topic.pk,
    title: topic.title,
    breadcrumbs,
  };
}

function _topicState(topic) {
  return {
    id: topic.pk,
    title: topic.title,
  };
}

function _topicsState(topics) {
  return topics.map(topic => _topicState(topic));
}

function _exerciseState(exercise) {
  return {
    id: exercise.pk,
    title: exercise.title,
  };
}

function _exercisesState(exercises) {
  return exercises.map(exercise => _exerciseState(exercise));
}

function _assignmentState(assignment) {
  return {
    assignmentId: String(assignment.id),
    collection: {
      id: String(assignment.collection.id),
      name: assignment.collection.name,
      kind: assignment.collection.kind,
    },
    examId: String(assignment.exam),
  };
}

function _assignmentsState(assignments) {
  return assignments.map(assignment => _assignmentState(assignment));
}

function _examState(exam) {
  const assignments = _assignmentsState(exam.assignments);
  const visibility = {};
  visibility.class = assignments.find(
    assignment => assignment.collection.kind === CollectionKinds.CLASSROOM);
  visibility.groups = assignments.filter(
    assignment => assignment.collection.kind === CollectionKinds.LEARNERGROUP);
  return {
    id: exam.id,
    title: exam.title,
    channelId: exam.channel_id,
    collection: exam.collection,
    active: exam.active,
    archive: exam.archive,
    questionCount: exam.question_count,
    questionSources: exam.question_sources,
    seed: exam.seed,
    visibility,
  };
}

function _examsState(exams, classId) {
  return exams.map(exam => _examState(exam, classId));
}

function displayExamModal(store, modalName) {
  store.dispatch('SET_EXAM_MODAL', modalName);
}

function showExamsPage(store, classId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.EXAMS);

  const resourceRequests = [
    ClassroomResource.getModel(classId).fetch(),
    LearnerGroupResource.getCollection({ parent: classId }).fetch(),
    ChannelResource.getCollection().fetch(),
    ExamResource.getCollection({ collection: classId }).fetch({}, true),
  ];

  return ConditionalPromise.all(resourceRequests).only(
    CoreActions.samePageCheckGenerator(store),
    ([classroom, learnerGroups, channels, exams]) => {
      const pageState = {
        channels: _channelsState(channels),
        classId,
        currentClass: pickIdAndName(classroom),
        currentClassGroups: learnerGroups.map(pickIdAndName),
        exams: _examsState(exams, classId),
        examModalShown: false,
      };

      store.dispatch('SET_PAGE_STATE', pageState);
      store.dispatch('CORE_SET_ERROR', null);
      store.dispatch('CORE_SET_TITLE', Constants.PageTitles.EXAMS);
      store.dispatch('CORE_SET_PAGE_LOADING', false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function activateExam(store, examId) {
  ExamResource.getModel(examId).save({ active: true }).then(
    () => {
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      exams[examIndex].active = true;

      store.dispatch('SET_EXAMS', exams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function deactivateExam(store, examId) {
  ExamResource.getModel(examId).save({ active: false }).then(
    () => {
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      exams[examIndex].active = false;

      store.dispatch('SET_EXAMS', exams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function _assignExamTo(examId, collection) {
  const assignmentPayload = {
    exam: examId,
    collection,
  };
  return new Promise((resolve, reject) => {
    ExamAssignmentResource.createModel(assignmentPayload).save().then(
      assignment => resolve(assignment),
      error => reject(error)
    );
  });
}

function _removeAssignment(assignmentId) {
  return new Promise((resolve, reject) => {
    ExamAssignmentResource.getModel(assignmentId).delete().then(
      () => resolve(),
      error => reject(error)
    );
  });
}

function updateExamAssignments(store, examId, collectionsToAssign, assignmentsToRemove) {
  const assignPromises = collectionsToAssign.map(collection => _assignExamTo(examId, collection));
  const unassignPromises = assignmentsToRemove.map(assignment => _removeAssignment(assignment));
  const assignmentPromises = assignPromises.concat(unassignPromises);

  ConditionalPromise.all(assignmentPromises).only(CoreActions.samePageCheckGenerator(store),
    response => {
      let newAssignments = response.filter(n => n);
      newAssignments = _assignmentsState(newAssignments);

      const classId = store.state.pageState.currentClass.id;
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      const examVisibility = exams[examIndex].visibility;

      newAssignments.forEach(assignment => {
        if (assignment.collection.id === classId) {
          examVisibility.class = assignment;
        } else {
          examVisibility.groups.push(assignment);
        }
      });

      assignmentsToRemove.forEach(assignmentId => {
        if (examVisibility.class) {
          if (assignmentId === examVisibility.class.assignmentId) {
            examVisibility.class = null;
            return;
          }
        }
        examVisibility.groups = examVisibility.groups.filter(
          group => group.assignmentId !== assignmentId);
      });

      exams[examIndex].visibility = examVisibility;
      store.dispatch('SET_EXAMS', exams);
      store.dispatch('CORE_SET_ERROR', null);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function previewExam(store) {
  displayExamModal(store, false);
}

function renameExam(store, examId, newExamTitle) {
  ExamResource.getModel(examId).save({ title: newExamTitle }).then(
    () => {
      const exams = store.state.pageState.exams;
      const examIndex = exams.findIndex(exam => exam.id === examId);
      exams[examIndex].title = newExamTitle;

      store.dispatch('SET_EXAMS', exams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function deleteExam(store, examId) {
  ExamResource.getModel(examId).delete().then(
    () => {
      const exams = store.state.pageState.exams;
      const updatedExams = exams.filter(exam => exam.id !== examId);

      store.dispatch('SET_EXAMS', updatedExams);
      displayExamModal(store, false);
    },
    error => CoreActions.handleError(store, error)
  );
}

function getAllExercisesWithinTopic(store, channelId, topicId) {
  return new Promise((resolve, reject) => {
    const exercisesPromise = ContentNodeResource.getDescendantsCollection(
      topicId,
      { channel_id: channelId },
      { descendant_kind: ContentNodeKinds.EXERCISE, fields: ['pk'] }
    ).fetch();

    ConditionalPromise.all([exercisesPromise]).only(
      CoreActions.samePageCheckGenerator(store),
      ([exercisesCollection]) => {
        const exercises = _exercisesState(exercisesCollection);
        const exerciseIds = exercises.map(exercise => exercise.id);
        resolve(exerciseIds);
      },
      error => reject(error)
    );
  });
}

// fetches topic, it's children subtopics, and children exercises
function fetchContent(store, channelId, topicId) {
  return new Promise((resolve, reject) => {
    const channelPayload = { channel_id: channelId };
    const topicPromise = ContentNodeResource.getModel(topicId, channelPayload).fetch();
    const subtopicsPromise = ContentNodeResource.getCollection(
      channelPayload, { parent: topicId, kind: ContentNodeKinds.TOPIC, fields: ['pk', 'title', 'ancestors'] }).fetch();
    const exercisesPromise = ContentNodeResource.getCollection(
      channelPayload, { parent: topicId, kind: ContentNodeKinds.EXERCISE, fields: ['pk', 'title'] }).fetch();

    ConditionalPromise.all([topicPromise, subtopicsPromise, exercisesPromise]).only(
      CoreActions.samePageCheckGenerator(store),
      ([topicModel, subtopicsCollection, exercisesCollection]) => {
        const topic = _currentTopicState(topicModel);
        const exercises = _exercisesState(exercisesCollection);
        let subtopics = _topicsState(subtopicsCollection);

        const subtopicsExercisesPromises = subtopics.map(
          subtopic => getAllExercisesWithinTopic(store, channelId, subtopic.id));

        ConditionalPromise.all(subtopicsExercisesPromises).only(
          CoreActions.samePageCheckGenerator(store),
          subtopicsExercises => {
            subtopics = subtopics.map((subtopic, index) => {
              subtopic.allExercisesWithinTopic = subtopicsExercises[index];
              return subtopic;
            });

            store.dispatch('SET_TOPIC', topic);
            store.dispatch('SET_SUBTOPICS', subtopics);
            store.dispatch('SET_EXERCISES', exercises);
            resolve({ topic, subtopics, exercises });
          },
          error => reject(error)
        );
      },
      error => reject(error)
    );
  });
}

function showCreateExamPage(store, classId, channelId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.CREATE_EXAM);
  store.dispatch('CORE_SET_TITLE', Constants.PageTitles.CREATE_EXAM);

  const currentClassPromise = ClassroomResource.getModel(classId).fetch();
  const channelPromise = ChannelResource.getCollection().fetch();

  ConditionalPromise.all([currentClassPromise, channelPromise]).only(
    CoreActions.samePageCheckGenerator(store),
    ([currentClassModel, channelsCollection]) => {
      const currentClass = pickIdAndName(currentClassModel);
      const currentChannel = _channelState(
        channelsCollection.find(channel => channel.id === channelId));

      const fetchContentPromise = fetchContent(store, channelId, currentChannel.rootPk);

      ConditionalPromise.all([fetchContentPromise]).only(
        CoreActions.samePageCheckGenerator(store),
        ([content]) => {
          const pageState = {
            currentClass,
            currentChannel,
            topic: content.topic,
            subtopics: content.subtopics,
            exercises: content.exercises,
            selectedExercises: [],
            examModalShown: false,
          };

          store.dispatch('SET_PAGE_STATE', pageState);
          store.dispatch('CORE_SET_ERROR', null);
          store.dispatch('CORE_SET_PAGE_LOADING', false);
        },
        error => CoreActions.handleError(store, error)
      );
    },
    error => CoreActions.handleError(store, error)
  );
}

function addExercise(store, exerciseId) {
  const selectedExercises = store.state.pageState.selectedExercises;
  if (!selectedExercises.includes(exerciseId)) {
    store.dispatch('SET_SELECTED_EXERCISES', selectedExercises.concat(exerciseId));
  }
}

function removeExercise(store, exerciseId) {
  const selectedExercises = store.state.pageState.selectedExercises;
  const index = selectedExercises.indexOf(exerciseId);
  if (index !== -1) {
    selectedExercises.splice(index, 1);
    store.dispatch('SET_SELECTED_EXERCISES', selectedExercises);
  }
}

function createExam(store, classCollection, examObj) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  const examPayload = {
    collection: examObj.classId,
    channel_id: examObj.channelId,
    title: examObj.title,
    question_count: examObj.numQuestions,
    question_sources: JSON.stringify(examObj.questionSources),
    seed: examObj.seed,
  };
  ExamResource.createModel(examPayload).save().then(
    exam => {
      _assignExamTo(exam.id, classCollection).then(
        () => {
          store.dispatch('CORE_SET_PAGE_LOADING', false);
          router.getInstance().push({ name: Constants.PageNames.EXAMS });
        },
        error => CoreActions.handleError(store, error)
      );
    },
    error => CoreActions.handleError(store, error)
  );
}

function showExamReportPage(store, classId, examId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.EXAM_REPORT);
  store.dispatch('SET_PAGE_STATE', {});
  store.dispatch('CORE_SET_ERROR', null);
  store.dispatch('CORE_SET_TITLE', ('Exam Report'));
  store.dispatch('CORE_SET_PAGE_LOADING', false);
}

function showExamReportDetailPage(store, classId, examId) {
  store.dispatch('CORE_SET_PAGE_LOADING', true);
  store.dispatch('SET_PAGE_NAME', Constants.PageNames.EXAM_REPORT_DETAIL);
  store.dispatch('SET_PAGE_STATE', {});
  store.dispatch('CORE_SET_ERROR', null);
  store.dispatch('CORE_SET_TITLE', ('Exam Report Detail'));
  store.dispatch('CORE_SET_PAGE_LOADING', false);
}

module.exports = {
  displayExamModal,
  showExamsPage,
  showCreateExamPage,
  showExamReportPage,
  showExamReportDetailPage,
  activateExam,
  deactivateExam,
  previewExam,
  renameExam,
  deleteExam,
  updateExamAssignments,
  fetchContent,
  createExam,
  addExercise,
  removeExercise,
  getAllExercisesWithinTopic,
};
<|endoftext|>


<commit_before>// Copyright (c) 2011 David Benjamin. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.

function positioned(elem) {
    var position = window.getComputedStyle(elem).position;
    return (position === "absolute" ||
	    position === "relative" ||
	    position === "fixed");
}

function hookTextArea(node, config) {
    function attachEmacs() {
	if (!node.parentNode)
	    return;
	var container = document.createElement("div");
	var embed = document.createElement("embed");
	embed.type = "application/x-embedded-emacs-container";
	embed.style.setProperty("width", "100%", "important");
	embed.style.setProperty("height", "100%", "important");
	embed.style.setProperty("padding", "0px", "important");
	embed.style.setProperty("margin", "0px", "important");
        // WebKit refuses to focus an embed element unless it has a tabIndex.
	embed.setAttribute("tabIndex", "0");
	container.appendChild(embed);
	container.style.setProperty("border", "1px solid black", "important");
	container.style.setProperty("padding", "0px", "important");
	container.style.setProperty("margin", "0px", "important");
	function relayout() {
	    container.style.setProperty("width", node.offsetWidth + "px",
					"important");
	    container.style.setProperty("height", node.offsetHeight + "px",
					"important");
	    // Apparently offsetParent is a lie.
	    var parent = node.offsetParent;
	    var top = node.offsetTop;
	    var left = node.offsetLeft;
	    var body = node.ownerDocument.body;
	    var docElem = node.ownerDocument.documentElement;
	    while (parent &&
		   parent !== body && parent !== docElem &&
		   !positioned(parent)) {
		top += parent.offsetTop;
		left += parent.offsetLeft;
		parent = parent.offsetParent;
	    }
	    container.style.setProperty("top", top + "px", "important");
	    container.style.setProperty("left", left + "px", "important");
	    if (window.getComputedStyle(node).position === "fixed") {
		container.style.setProperty("position", "fixed", "important");
	    } else {
		container.style.setProperty("position", "absolute", "important");
	    }
	}
	relayout();
	node.parentNode.insertBefore(container, node);

	function onAttrModified(ev) {
	    if (ev.attrName !== "style")
		return;
	    relayout();
	}
	// FIXME: This doesn't actually catch CSS changes.
	node.addEventListener("DOMAttrModified", onAttrModified);
	window.addEventListener("resize", relayout);

	// FIXME: If another script decides to change this value
	// again, act accordingly.
	var oldVisibility = node.style.visibility;
	node.style.visibility = "hidden";

	chrome.extension.sendRequest({
	    type: "start_editor",
            text: node.value,
            windowId: embed.windowId
	}, function (text) {
	    node.removeEventListener("DOMAttrModified", onAttrModified);
	    window.removeEventListener("resize", relayout);

	    node.value = text;
	    node.style.visibility = oldVisibility;
	    if (container.parentNode)
		container.parentNode.removeChild(container);
	});

	// FIXME: This doesn't work.
	embed.focus();
    }

    if (config.triggerAltX)
        // Apparently I don't get keypress for Alt-X.
        node.addEventListener('keydown', function (evt) {
            if ((evt.altKey || evt.metaKey) &&
                !evt.ctrlKey &&
                !evt.shiftKey &&
                evt.keyCode === 88) {
                attachEmacs();
            }
        });
    if (config.triggerDoubleClick)
        node.addEventListener('dblclick', attachEmacs);
}

chrome.extension.sendRequest({
    type: 'get_config'
}, function (config) {
    if (config.enabled) {
        // Hook the current textareas.
        var textareas = document.getElementsByTagName("textarea");
        for (var i = 0; i < textareas.length; i++) {
            hookTextArea(textareas[i], config);
        }

        // And hook any new ones that get created.
        document.body.addEventListener('DOMNodeInserted', function (ev) {
            if (ev.target.nodeType != 1)
                        return;
            var textareas = ev.target.getElementsByTagName("textarea");
            for (var i = 0; i < textareas.length; i++) {
                hookTextArea(textareas[i], config);
            }
        });
    }
});
<commit_msg>If we couldn't load the plugin, abort<commit_after>// Copyright (c) 2011 David Benjamin. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.

function positioned(elem) {
    var position = window.getComputedStyle(elem).position;
    return (position === "absolute" ||
	    position === "relative" ||
	    position === "fixed");
}

function hookTextArea(node, config) {
    function attachEmacs() {
	if (!node.parentNode)
	    return;
	var container = document.createElement("div");
	var embed = document.createElement("embed");
	embed.type = "application/x-embedded-emacs-container";
	embed.style.setProperty("width", "100%", "important");
	embed.style.setProperty("height", "100%", "important");
	embed.style.setProperty("padding", "0px", "important");
	embed.style.setProperty("margin", "0px", "important");
        // WebKit refuses to focus an embed element unless it has a tabIndex.
	embed.setAttribute("tabIndex", "0");
	container.appendChild(embed);
	container.style.setProperty("border", "1px solid black", "important");
	container.style.setProperty("padding", "0px", "important");
	container.style.setProperty("margin", "0px", "important");
	function relayout() {
	    container.style.setProperty("width", node.offsetWidth + "px",
					"important");
	    container.style.setProperty("height", node.offsetHeight + "px",
					"important");
	    // Apparently offsetParent is a lie.
	    var parent = node.offsetParent;
	    var top = node.offsetTop;
	    var left = node.offsetLeft;
	    var body = node.ownerDocument.body;
	    var docElem = node.ownerDocument.documentElement;
	    while (parent &&
		   parent !== body && parent !== docElem &&
		   !positioned(parent)) {
		top += parent.offsetTop;
		left += parent.offsetLeft;
		parent = parent.offsetParent;
	    }
	    container.style.setProperty("top", top + "px", "important");
	    container.style.setProperty("left", left + "px", "important");
	    if (window.getComputedStyle(node).position === "fixed") {
		container.style.setProperty("position", "fixed", "important");
	    } else {
		container.style.setProperty("position", "absolute", "important");
	    }
	}
	relayout();
	node.parentNode.insertBefore(container, node);

        // Make sure the plugin loaded.
        if (!embed.hasOwnProperty('windowId')) {
            console.log('Could not load container');
            container.parentNode.removeChild(container);
            return;
        }

	function onAttrModified(ev) {
	    if (ev.attrName !== "style")
		return;
	    relayout();
	}
	// FIXME: This doesn't actually catch CSS changes.
	node.addEventListener("DOMAttrModified", onAttrModified);
	window.addEventListener("resize", relayout);

	// FIXME: If another script decides to change this value
	// again, act accordingly.
	var oldVisibility = node.style.visibility;
	node.style.visibility = "hidden";

	chrome.extension.sendRequest({
	    type: "start_editor",
            text: node.value,
            windowId: embed.windowId
	}, function (text) {
	    node.removeEventListener("DOMAttrModified", onAttrModified);
	    window.removeEventListener("resize", relayout);

	    node.value = text;
	    node.style.visibility = oldVisibility;
	    if (container.parentNode)
		container.parentNode.removeChild(container);
	});

	// FIXME: This doesn't work.
	embed.focus();
    }

    if (config.triggerAltX)
        // Apparently I don't get keypress for Alt-X.
        node.addEventListener('keydown', function (evt) {
            if ((evt.altKey || evt.metaKey) &&
                !evt.ctrlKey &&
                !evt.shiftKey &&
                evt.keyCode === 88) {
                attachEmacs();
            }
        });
    if (config.triggerDoubleClick)
        node.addEventListener('dblclick', attachEmacs);
}

chrome.extension.sendRequest({
    type: 'get_config'
}, function (config) {
    if (config.enabled) {
        // Hook the current textareas.
        var textareas = document.getElementsByTagName("textarea");
        for (var i = 0; i < textareas.length; i++) {
            hookTextArea(textareas[i], config);
        }

        // And hook any new ones that get created.
        document.body.addEventListener('DOMNodeInserted', function (ev) {
            if (ev.target.nodeType != 1)
                        return;
            var textareas = ev.target.getElementsByTagName("textarea");
            for (var i = 0; i < textareas.length; i++) {
                hookTextArea(textareas[i], config);
            }
        });
    }
});
<|endoftext|>


<commit_before>/* The ViewManager takes care of detecting whether the user uses the old or
 * new Compose in Gmail. Once it finds out, it takes care of instantiating
 * the appropriate view (regularView or updatedView).
 */
var ViewManager = function () {
    if (arguments.callee.singleton_instance) {
        return arguments.callee.singleton_instance;
    }

    arguments.callee.singleton_instance = new (function () {
        var observerCallbacks = [];
        var cloudy_view;
        var mytimer;
        var enabled;

        this.addViewObserver = function (fn) {
            observerCallbacks.push(fn);
        }

        this.getView = function(){
            return cloudy_view;
        }

        /* Look for an element named "subject". If found, then a Compose window
         * must be open. If a DOM element with name "from" exists, and it is of
         * type "input", then we must be encountering the new Gmail Compose.
         * Otherwise, we're dealing with the old one.
         */
        var checkCompose = function() {
            if (enabled && document.getElementsByName("subject").length) {
                if (!document.getElementsByClassName("I5").length) {
                    // Gmail's old interface
                    cloudy_view = new RegularView();
                } else {
                    // Gmail's new interface
                    cloudy_view = new UpdatedView();
                }
                for (var i = 0; i < observerCallbacks.length; i++) {
                    cloudy_view.addObserver(observerCallbacks[i]);
                }
                clearInterval(mytimer);
                enabled = false;
            }
        }

        var loadSocialButtons = function() {
            // Load Tweet button
            (!function(d,s,id) {
                console.log("loading twitter");
                var js,fjs=d.getElementsByTagName(s)[0];
                if(!d.getElementById(id)) {
                    js=d.createElement(s);
                    js.id=id;
                    js.async = true;
                    js.src="https://platform.twitter.com/widgets.js";
                    fjs.parentNode.insertBefore(js,fjs);
                }
            } (document,"script","twitter-wjs"));

            // Load Google +1 button
            (function(d, s, id) {
                console.log("loading G+");
                var po = d.createElement(s);
                if (d.getElementById(id)) return;
                po.type = 'text/javascript';
                po.async = true;
                po.src = 'https://apis.google.com/js/plusone.js';
                var s = d.getElementsByTagName('script')[0];
                s.parentNode.insertBefore(po, s);
            })(document, 'script', 'googleplus-jssdk');

            // Load Pinterest "Pin it" button
            (function(d, s, id) {
                console.log("loading Pinterest");
                var js, fjs = d.getElementsByTagName(s)[0];
                if (d.getElementById(id)) return;
                js = d.createElement(s);
                js.id = id;
                js.src = "https://assets.pinterest.com/js/pinit.js";
                js.async = true;
                fjs.parentNode.insertBefore(js, fjs);
            } (document, 'script', 'pinterest-jssdk'));
        }

        var init = function() {
            mytimer = setInterval(checkCompose, 500);
            enabled = true;

            // check for promo bubble, display it if loaded
            var notification_bubble = document.getElementById("cloudy_bubble");
            if (notification_bubble) {
                console.log("Showing notification");

                if ($jQcl(notification_bubble).hasClass("cloudy_social")) {
                    loadSocialButtons();
                }
                /* why does this not work? */
                //$jQcl(notification_bubble).show();
                notification_bubble.style.display = "block";
                $jQcl(notification_bubble).delay(1500).fadeTo(1000, 1,
                        function() {
                    var cloudy_events =
                        document.getElementById("cloudy_events");
                    if (cloudy_events) {
                        var e = document.createEvent("Events");
                        e.initEvent("cloudy_notificationDisplayed",
                            false, true);
                        cloudy_events.dispatchEvent(e);
                    }
                });
                $jQcl("#cloudy_bubble_close").click(function(){
                    $jQcl(notification_bubble).fadeTo(600, 0, function(){
                        notification_bubble.hide();
                        //notification_bubble.parentNode.removeChild(
                        //    notification_bubble);
                    });
                });
            }
        }

        init.call(this);
        return this;
    })();

    return arguments.callee.singleton_instance;
}
<commit_msg>bug with calling hide() on non-jQuery element<commit_after>/* The ViewManager takes care of detecting whether the user uses the old or
 * new Compose in Gmail. Once it finds out, it takes care of instantiating
 * the appropriate view (regularView or updatedView).
 */
var ViewManager = function () {
    if (arguments.callee.singleton_instance) {
        return arguments.callee.singleton_instance;
    }

    arguments.callee.singleton_instance = new (function () {
        var observerCallbacks = [];
        var cloudy_view;
        var mytimer;
        var enabled;

        this.addViewObserver = function (fn) {
            observerCallbacks.push(fn);
        }

        this.getView = function(){
            return cloudy_view;
        }

        /* Look for an element named "subject". If found, then a Compose window
         * must be open. If a DOM element with name "from" exists, and it is of
         * type "input", then we must be encountering the new Gmail Compose.
         * Otherwise, we're dealing with the old one.
         */
        var checkCompose = function() {
            if (enabled && document.getElementsByName("subject").length) {
                if (!document.getElementsByClassName("I5").length) {
                    // Gmail's old interface
                    cloudy_view = new RegularView();
                } else {
                    // Gmail's new interface
                    cloudy_view = new UpdatedView();
                }
                for (var i = 0; i < observerCallbacks.length; i++) {
                    cloudy_view.addObserver(observerCallbacks[i]);
                }
                clearInterval(mytimer);
                enabled = false;
            }
        }

        var loadSocialButtons = function() {
            // Load Tweet button
            (!function(d,s,id) {
                console.log("loading twitter");
                var js,fjs=d.getElementsByTagName(s)[0];
                if(!d.getElementById(id)) {
                    js=d.createElement(s);
                    js.id=id;
                    js.async = true;
                    js.src="https://platform.twitter.com/widgets.js";
                    fjs.parentNode.insertBefore(js,fjs);
                }
            } (document,"script","twitter-wjs"));

            // Load Google +1 button
            (function(d, s, id) {
                console.log("loading G+");
                var po = d.createElement(s);
                if (d.getElementById(id)) return;
                po.type = 'text/javascript';
                po.async = true;
                po.src = 'https://apis.google.com/js/plusone.js';
                var s = d.getElementsByTagName('script')[0];
                s.parentNode.insertBefore(po, s);
            })(document, 'script', 'googleplus-jssdk');

            // Load Pinterest "Pin it" button
            (function(d, s, id) {
                console.log("loading Pinterest");
                var js, fjs = d.getElementsByTagName(s)[0];
                if (d.getElementById(id)) return;
                js = d.createElement(s);
                js.id = id;
                js.src = "https://assets.pinterest.com/js/pinit.js";
                js.async = true;
                fjs.parentNode.insertBefore(js, fjs);
            } (document, 'script', 'pinterest-jssdk'));
        }

        var init = function() {
            mytimer = setInterval(checkCompose, 500);
            enabled = true;

            // check for promo bubble, display it if loaded
            var notification_bubble = $jQcl("#cloudy_bubble");
            if (notification_bubble && notification_bubble.length > 0) {
                console.log("Showing notification");

                if (notification_bubble.hasClass("cloudy_social")) {
                    loadSocialButtons();
                }
                /* why does this not work? */
                //notification_bubble.show();
                notification_bubble.css("display", "block");
                notification_bubble.delay(1500).fadeTo(1000, 1,
                        function() {
                    var cloudy_events =
                        document.getElementById("cloudy_events");
                    if (cloudy_events) {
                        var e = document.createEvent("Events");
                        e.initEvent("cloudy_notificationDisplayed",
                            false, true);
                        cloudy_events.dispatchEvent(e);
                    }
                });
                $jQcl("#cloudy_bubble_close").click(function(){
                    notification_bubble.fadeTo(600, 0, function(){
                        notification_bubble.hide();
                        //notification_bubble.parentNode.removeChild(
                        //    notification_bubble);
                    });
                });
            }
        }

        init.call(this);
        return this;
    })();

    return arguments.callee.singleton_instance;
}
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var check = require('check-types');
var parseCode = require('./parser').parseCode;
var parseUnitTestCode = require('./parserUnitTest').parseUnitTestCode;

var apiComments = null;

module.exports = function (apiJson, htmlFilename) {
	check.verifyArray(apiJson, 'missing api array');
	check.verifyString(htmlFilename, 'missing output filename');

	console.log('generating html to', htmlFilename);
	var title = 'Sample API';
	var o = '<!DOCTYPE HTML>\n';
	o += '\n\n';
	o += '\t<title>Api</title>\n';
	var css = fileContents('./api.css');
	check.verifyString(css, 'could not get css');
	o += '\t\n';
	o += '\n';

	var script = fileContents('./toggle.js');
	check.verifyString(script, 'could not get toggle script');
	o += '\n';
	o += '\n';
	o += '\n';
	o += '
' + title + ' by xplain
\n';
	o += '\t
\n';

	var methodDocs = [];
	apiComments = apiJson;
	apiComments.forEach(function (apiComment) {
		console.log('checking comment', apiComment);
		if (!isMethod(apiComment)) {
			return;
		}
		console.log('found method comment');
		var info = methodDiv(apiComment);
		check.verifyString(info.name, 'did not get method name string');
		check.verifyString(info.docs, 'did not get method docs string');
		methodDocs.push(info);
	});

	var indexStr = '';
	var docsStr = '';
	methodDocs.forEach(function (info) {
		indexStr += info.name + '\n';
		docsStr += info.docs + '\n';
	});

	o += '\t\t
\n' + indexStr + '\t\t
\n';
	o += '\t\t
\n' + docsStr + '\t\t
\n';
	o += '\t
\n'; // content
	o += '\n';
	o += '\n';
	fs.writeFileSync(htmlFilename, o, 'utf-8');
};

function fileContents(name) {
	check.verifyString(name, 'missing file name');
	var cssFilename = path.join(__dirname, name);
	var cssText = fs.readFileSync(cssFilename, 'utf8');
	return cssText;
}

function isMethod(apiComment) {
	if (!Array.isArray(apiComment.tags)) {
		return false;
	}
	return apiComment.tags.some(function (tag) {
		return tag.type === 'method';
	});
}

function isExampleFor(apiComment, name) {
	if (!Array.isArray(apiComment.tags)) {
		return false;
	}
	return apiComment.tags.some(function (tag) {
		return (tag.type === 'exampleFor' || tag.type === 'example')
			&& tag.string === name;
	});
}

function isSampleFor(apiComment, name) {
	if (!Array.isArray(apiComment.tags)) {
		return false;
	}
	return apiComment.tags.some(function (tag) {
		return (tag.type === 'sampleFor' || tag.type === 'sample')
			&& tag.string === name;
	});
}

function examplesFor(name) {
	check.verifyString(name, 'missing name');
	check.verifyArray(apiComments, 'missing api comments');
	var apiExamples = apiComments.filter(function (apiComment) {
		return isExampleFor(apiComment, name);
	});
	console.log('have', apiExamples.length, 'examples for', name);
	// console.log(apiExamples);

	var examples = apiExamples.map(function (example) {
		console.log('example', example);
		return exampleDiv(name, example);
	});
	// console.log('examples', examples);
	// return examples.join('\n');
	return examples;
}

function samplesFor(name) {
	check.verifyString(name, 'missing name');
	check.verifyArray(apiComments, 'missing api comments');
	var apiSamples = apiComments.filter(function (apiComment) {
		return isSampleFor(apiComment, name);
	});
	console.log('have', apiSamples.length, 'samples for', name);
	console.log(apiSamples);
	var samples = apiSamples.map(sampleDiv);
	console.log('samples', samples);
	return samples.join('\n');
}

var exampleDivId = 0;
function exampleDiv(name, apiExample) {
	check.verifyString(name, 'missing method name');
	check.verifyObject(apiExample, 'missing example code string');

	var id = name + '_example_' + ++exampleDivId + '_toggle';
	var toggle = '<input class="toggle" type="button" value="example ' + exampleDivId + '" id="' + id + '">\n';
	var o = '
\n';
	o += '
\n' + apiExample.code + '\n
\n';
	o += '
\n';
	return {
		toggle: toggle,
		code: o
	};
}

function sampleToCommentLike(testCode) {
	check.verifyString(testCode, 'missing test code');
	var parsed = parseCode(testCode);
	check.verifyObject(parsed, 'could not parse\n' + testCode);
	return parsed;
}

var sampleDivId = 1;
function sampleDiv(apiExample) {
	var o = '
\n';
	var code = apiExample.code;
	check.verifyString(code, 'missing code for sample');
	var parsed = sampleToCommentLike(code);
	check.verifyObject(parsed, 'did not get sample from', code);
	check.verifyString(parsed.name, 'there is no name for', code);
	check.verifyString(parsed.code, 'there is no code for', code);
	var humanForm = parseUnitTestCode(parsed.code);
	console.log('human form', humanForm);
	if (!check.isString(humanForm)) {
		console.log('could not convert', parseCode, 'to human form');
		humanForm = parsed.code;
	}
	check.verifyString(humanForm, 'could not convert to human form', parsed.code);
	o += '' + parsed.name + '\n';
	o += '
\n';
	o += ''
	o += humanForm;
	o += '\n';
	o += '
\n';
	o += '
\n';
	return o;
}

function methodDiv(apiComment) {
	check.verifyObject(apiComment, 'missing api comment object');
	console.assert(apiComment.ctx, 'missing ctx property');
	console.assert(apiComment.ctx.type === 'function', 'ctx is not function');
	check.verifyString(apiComment.ctx.name, 'missing function name');
	var name = apiComment.ctx.name;

	var o = '
\n';
	o += '
' + name + '
\n';
	o += apiComment.description.summary + '\n';

	var samples = samplesFor(name);
	if (samples) {
		o += samples + '\n';
	}

	var toggles = '';
	var examplesText = '';

	var examples = examplesFor(name);
	check.verifyArray(examples, 'could not get examples tags');
	examples.forEach(function (example) {
		toggles += example.toggle + '\n';
		examplesText += example.code + '\n';
	});

	var id = name + '_code_toggle';
	var toggle = '<input class="toggle" type="button" value="code" id="' + id + '">\n';
	toggles += toggle;

	o += toggles + '\n';
	o += examplesText + '\n';
	o += '
\n';
	o += '
\n' + apiComment.code + '\n
\n';
	o += '
\n';

	o += '
\n';

	var nameDiv = '
'
		+ name + '
';
	return {
		name: nameDiv,
		docs: o
	};
}<commit_msg>google syntax highlighter used for code samples<commit_after>var fs = require('fs');
var path = require('path');
var check = require('check-types');
var parseCode = require('./parser').parseCode;
var parseUnitTestCode = require('./parserUnitTest').parseUnitTestCode;

var apiComments = null;

module.exports = function (apiJson, htmlFilename) {
	check.verifyArray(apiJson, 'missing api array');
	check.verifyString(htmlFilename, 'missing output filename');

	console.log('generating html to', htmlFilename);
	var title = 'Sample API';
	var o = '<!DOCTYPE HTML>\n';
	o += '\n\n';
	o += '\t<title>Api</title>\n';
	var css = fileContents('./api.css');
	check.verifyString(css, 'could not get css');
	o += '\t\n';
	o += '\n';
	o += '\n';

	var script = fileContents('./toggle.js');
	check.verifyString(script, 'could not get toggle script');
	o += '\n';
	o += '\n';
	o += '\n';
	o += '
' + title + ' by xplain
\n';
	o += '\t
\n';

	var methodDocs = [];
	apiComments = apiJson;
	apiComments.forEach(function (apiComment) {
		console.log('checking comment', apiComment);
		if (!isMethod(apiComment)) {
			return;
		}
		console.log('found method comment');
		var info = methodDiv(apiComment);
		check.verifyString(info.name, 'did not get method name string');
		check.verifyString(info.docs, 'did not get method docs string');
		methodDocs.push(info);
	});

	var indexStr = '';
	var docsStr = '';
	methodDocs.forEach(function (info) {
		indexStr += info.name + '\n';
		docsStr += info.docs + '\n';
	});

	o += '\t\t
\n' + indexStr + '\t\t
\n';
	o += '\t\t
\n' + docsStr + '\t\t
\n';
	o += '\t
\n'; // content
	o += '\n';
	o += '\n';
	fs.writeFileSync(htmlFilename, o, 'utf-8');
};

function fileContents(name) {
	check.verifyString(name, 'missing file name');
	var cssFilename = path.join(__dirname, name);
	var cssText = fs.readFileSync(cssFilename, 'utf8');
	return cssText;
}

function isMethod(apiComment) {
	if (!Array.isArray(apiComment.tags)) {
		return false;
	}
	return apiComment.tags.some(function (tag) {
		return tag.type === 'method';
	});
}

function isExampleFor(apiComment, name) {
	if (!Array.isArray(apiComment.tags)) {
		return false;
	}
	return apiComment.tags.some(function (tag) {
		return (tag.type === 'exampleFor' || tag.type === 'example')
			&& tag.string === name;
	});
}

function isSampleFor(apiComment, name) {
	if (!Array.isArray(apiComment.tags)) {
		return false;
	}
	return apiComment.tags.some(function (tag) {
		return (tag.type === 'sampleFor' || tag.type === 'sample')
			&& tag.string === name;
	});
}

function examplesFor(name) {
	check.verifyString(name, 'missing name');
	check.verifyArray(apiComments, 'missing api comments');
	var apiExamples = apiComments.filter(function (apiComment) {
		return isExampleFor(apiComment, name);
	});
	console.log('have', apiExamples.length, 'examples for', name);
	// console.log(apiExamples);

	var examples = apiExamples.map(function (example) {
		console.log('example', example);
		return exampleDiv(name, example);
	});
	// console.log('examples', examples);
	// return examples.join('\n');
	return examples;
}

function samplesFor(name) {
	check.verifyString(name, 'missing name');
	check.verifyArray(apiComments, 'missing api comments');
	var apiSamples = apiComments.filter(function (apiComment) {
		return isSampleFor(apiComment, name);
	});
	console.log('have', apiSamples.length, 'samples for', name);
	console.log(apiSamples);
	var samples = apiSamples.map(sampleDiv);
	console.log('samples', samples);
	return samples.join('\n');
}

var exampleDivId = 0;
function exampleDiv(name, apiExample) {
	check.verifyString(name, 'missing method name');
	check.verifyObject(apiExample, 'missing example code string');

	var id = name + '_example_' + ++exampleDivId + '_toggle';
	var toggle = '<input class="toggle" type="button" value="example ' + exampleDivId + '" id="' + id + '">\n';
	var o = '
\n';
	o += '
\n' + apiExample.code + '\n
\n';
	o += '
\n';
	return {
		toggle: toggle,
		code: o
	};
}

function sampleToCommentLike(testCode) {
	check.verifyString(testCode, 'missing test code');
	var parsed = parseCode(testCode);
	check.verifyObject(parsed, 'could not parse\n' + testCode);
	return parsed;
}

var sampleDivId = 1;
function sampleDiv(apiExample) {
	var o = '
\n';
	var code = apiExample.code;
	check.verifyString(code, 'missing code for sample');
	var parsed = sampleToCommentLike(code);
	check.verifyObject(parsed, 'did not get sample from', code);
	check.verifyString(parsed.name, 'there is no name for', code);
	check.verifyString(parsed.code, 'there is no code for', code);
	var humanForm = parseUnitTestCode(parsed.code);
	console.log('human form', humanForm);
	if (!check.isString(humanForm)) {
		console.log('could not convert', parseCode, 'to human form');
		humanForm = parsed.code;
	}
	check.verifyString(humanForm, 'could not convert to human form', parsed.code);
	o += '' + parsed.name + '\n';
	o += '
\n';
	o += ''
	o += humanForm;
	o += '\n';
	o += '
\n';
	o += '
\n';
	return o;
}

function methodDiv(apiComment) {
	check.verifyObject(apiComment, 'missing api comment object');
	console.assert(apiComment.ctx, 'missing ctx property');
	console.assert(apiComment.ctx.type === 'function', 'ctx is not function');
	check.verifyString(apiComment.ctx.name, 'missing function name');
	var name = apiComment.ctx.name;

	var o = '
\n';
	o += '
' + name + '
\n';
	o += apiComment.description.summary + '\n';

	var samples = samplesFor(name);
	if (samples) {
		o += samples + '\n';
	}

	var toggles = '';
	var examplesText = '';

	var examples = examplesFor(name);
	check.verifyArray(examples, 'could not get examples tags');
	examples.forEach(function (example) {
		toggles += example.toggle + '\n';
		examplesText += example.code + '\n';
	});

	var id = name + '_code_toggle';
	var toggle = '<input class="toggle" type="button" value="code" id="' + id + '">\n';
	toggles += toggle;

	o += toggles + '\n';
	o += examplesText + '\n';
	o += '
\n';
	o += '
\n' + apiComment.code + '
\n';
	o += '
\n';

	o += '
\n';

	var nameDiv = '
'
		+ name + '
';
	return {
		name: nameDiv,
		docs: o
	};
}<|endoftext|>


<commit_before>/*
 * Copyright (C) 2012 - 2014  Bo Zhu  http://zhuzhu.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/*jslint browser: true */
/*global $: false, chrome: false, ga_report_event: false */

function set_i18n_text() {
    "use strict";
    var get_msg = chrome.i18n.getMessage;

    $('div#mode_select strong').html(get_msg('mode_select'));

    $('span.mode_lite_name').html(get_msg('mode_lite'));
    $('span.mode_lite_desc').html(get_msg('mode_lite_description'));
    $('span.mode_normal_name').html(get_msg('mode_normal'));
    $('span.mode_normal_desc').html(get_msg('mode_normal_description'));
    $('span.mode_redirect_name').html(get_msg('mode_redirect'));
    $('span.mode_redirect_desc').html(get_msg('mode_redirect_description'));

    $('div#help_text').html(get_msg('help'));
    $('div#feedback').html(get_msg('feedback'));
    $('div#rating').html(get_msg('rating'));
    $('span#sharing_text').html(get_msg('sharing'));

    $('div#support_title strong').html(get_msg('support_title'));
    $('span#support_checkbox_label').html(get_msg('support_checkbox_label'));
}

function is_opera() {
    return !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
}

$(document).ready(function() {
    "use strict";
    set_i18n_text();

    var background = chrome.extension.getBackgroundPage();

    // set default button display
    background.get_mode_name(function(current_mode_name) {
        switch (current_mode_name) {
            case 'lite':
                $('label#lite').addClass('active');
                break;
            case 'redirect':
                $('label#redirect').addClass('active');
                break;
            default:
                $('label#normal').addClass('active');
                break;
        }

        // append system info to the wufoo feedback link
        // such as extension version, chrome version, and os type
        var locale = navigator.language.substr(0, 2);
        if (locale === 'en' || locale === 'zh') {
            // jQuery.browser is not always accurate
            var system_info = 'Unblock Youku ' + background.unblock_youku.version;
            system_info += ' (' + current_mode_name + ', ' + locale + '); ';
            system_info += navigator.userAgent;
            console.log(system_info);

            var feedback_url = $('#feedback a');
            feedback_url.prop('href', feedback_url.prop('href') + '/def/field13=' + encodeURIComponent(system_info));
        }
    });

    // check whether the browser is Opera, to get around Flash bug in Chrome
    // https://github.com/zhuzhuor/Unblock-Youku/issues/209
    if(!is_opera()) {
        $('#redirect').addClass('disabled');
        $('#input_redirect').attr('disabled', true);

        // $('#buttons').tooltip({html: true, title: chrome.i18n.getMessage('mode_redirect_disabled')});
        // $('#input_redirect').tooltip({html: true, title: chrome.i18n.getMessage('mode_redirect_disabled')});
        $('.mode_redirect_desc').parents('tr').addClass('text-muted').tooltip({html: true, title: chrome.i18n.getMessage('mode_redirect_disabled')});

        // if current mode is redirect, change to normal mode
        background.get_mode_name(function(current_mode_name) {
            if(current_mode_name === 'redirect') {
                $('#normal').click();
            }
        });
    }

    var pre_heart_icon = ' ';

    background.get_storage('support_us', function(option) {
        if (option === 'yes') {
            $('#support_checkbox input').prop('checked', true);
            $('div#support_message').html(pre_heart_icon + chrome.i18n.getMessage('support_message_yes'));
        } else {
            $('#support_checkbox input').prop('checked', false);
            $('div#support_message').html(chrome.i18n.getMessage('support_message_no'));
        }
    });

    $('#support_checkbox input').click(function() {
        if ($('#support_checkbox input').prop('checked')) {
            background.set_storage('support_us', 'yes', function() {
                $('div#support_message').html(pre_heart_icon + chrome.i18n.getMessage('support_message_yes'));
                console.log('change to support us');
                ga_report_event('Change Support', 'Yes');
            });
        } else {
            background.set_storage('support_us', 'no', function() {
                $('div#support_message').html(chrome.i18n.getMessage('support_message_no'));
                console.log('change to not support us');
                ga_report_event('Change Support', 'No');
            });
        }
    });

    chrome.browserAction.setBadgeText({text: ''});  // clear the text NEW
    background.get_storage('previous_new_version', function(version) {
        if (typeof version === 'undefined' || version !== background.unblock_youku.lastest_new_version) {
            background.set_storage('previous_new_version', background.unblock_youku.lastest_new_version);
        }
    });

    // $('div#version').html('Unblock Youku  ' + background.unblock_youku.version);

    // button actions
    $('input#input_lite').change(function() {
        console.log('to change mode to lite');
        background.change_mode('lite');
    });
    $('input#input_normal').change(function() {
        console.log('to change mode to normal');
        background.change_mode('normal');
    });
    $('input#input_redirect').change(function() {
        console.log('to change mode to redirect');
        background.change_mode('redirect');
    });

    var my_date = new Date();
    if (typeof localStorage.first_time === 'undefined') {
        localStorage.first_time = my_date.getTime();
    } else if (my_date.getTime() > parseInt(localStorage.first_time, 10) + 1000 * 60 * 60 * 24 * 3) {
        $('div#rating').show(); // delay 3 days for the rating div to show up, hahaha
    }
});

<commit_msg>Fixed upstream #209 detect Flash bug<commit_after>/*
 * Copyright (C) 2012 - 2014  Bo Zhu  http://zhuzhu.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/*jslint browser: true */
/*global $: false, chrome: false, ga_report_event: false */

function set_i18n_text() {
    "use strict";
    var get_msg = chrome.i18n.getMessage;

    $('div#mode_select strong').html(get_msg('mode_select'));

    $('span.mode_lite_name').html(get_msg('mode_lite'));
    $('span.mode_lite_desc').html(get_msg('mode_lite_description'));
    $('span.mode_normal_name').html(get_msg('mode_normal'));
    $('span.mode_normal_desc').html(get_msg('mode_normal_description'));
    $('span.mode_redirect_name').html(get_msg('mode_redirect'));
    $('span.mode_redirect_desc').html(get_msg('mode_redirect_description'));

    $('div#help_text').html(get_msg('help'));
    $('div#feedback').html(get_msg('feedback'));
    $('div#rating').html(get_msg('rating'));
    $('span#sharing_text').html(get_msg('sharing'));

    $('div#support_title strong').html(get_msg('support_title'));
    $('span#support_checkbox_label').html(get_msg('support_checkbox_label'));
}

function is_flash_bug_fixed() {
    return !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0 || parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10) >= 38;
}

$(document).ready(function() {
    "use strict";
    set_i18n_text();

    var background = chrome.extension.getBackgroundPage();

    // set default button display
    background.get_mode_name(function(current_mode_name) {
        switch (current_mode_name) {
            case 'lite':
                $('label#lite').addClass('active');
                break;
            case 'redirect':
                $('label#redirect').addClass('active');
                break;
            default:
                $('label#normal').addClass('active');
                break;
        }

        // append system info to the wufoo feedback link
        // such as extension version, chrome version, and os type
        var locale = navigator.language.substr(0, 2);
        if (locale === 'en' || locale === 'zh') {
            // jQuery.browser is not always accurate
            var system_info = 'Unblock Youku ' + background.unblock_youku.version;
            system_info += ' (' + current_mode_name + ', ' + locale + '); ';
            system_info += navigator.userAgent;
            console.log(system_info);

            var feedback_url = $('#feedback a');
            feedback_url.prop('href', feedback_url.prop('href') + '/def/field13=' + encodeURIComponent(system_info));
        }
    });

    // check whether the browser is Opera, or Chrome version >= 38, to get around Flash bug in Chrome
    // https://github.com/zhuzhuor/Unblock-Youku/issues/209
    if(!is_flash_bug_fixed()) {
        $('#redirect').addClass('disabled');
        $('#input_redirect').attr('disabled', true);

        // $('#buttons').tooltip({html: true, title: chrome.i18n.getMessage('mode_redirect_disabled')});
        // $('#input_redirect').tooltip({html: true, title: chrome.i18n.getMessage('mode_redirect_disabled')});
        $('.mode_redirect_desc').parents('tr').addClass('text-muted').tooltip({html: true, title: chrome.i18n.getMessage('mode_redirect_disabled')});

        // if current mode is redirect, change to normal mode
        background.get_mode_name(function(current_mode_name) {
            if(current_mode_name === 'redirect') {
                $('#normal').click();
            }
        });
    }

    var pre_heart_icon = ' ';

    background.get_storage('support_us', function(option) {
        if (option === 'yes') {
            $('#support_checkbox input').prop('checked', true);
            $('div#support_message').html(pre_heart_icon + chrome.i18n.getMessage('support_message_yes'));
        } else {
            $('#support_checkbox input').prop('checked', false);
            $('div#support_message').html(chrome.i18n.getMessage('support_message_no'));
        }
    });

    $('#support_checkbox input').click(function() {
        if ($('#support_checkbox input').prop('checked')) {
            background.set_storage('support_us', 'yes', function() {
                $('div#support_message').html(pre_heart_icon + chrome.i18n.getMessage('support_message_yes'));
                console.log('change to support us');
                ga_report_event('Change Support', 'Yes');
            });
        } else {
            background.set_storage('support_us', 'no', function() {
                $('div#support_message').html(chrome.i18n.getMessage('support_message_no'));
                console.log('change to not support us');
                ga_report_event('Change Support', 'No');
            });
        }
    });

    chrome.browserAction.setBadgeText({text: ''});  // clear the text NEW
    background.get_storage('previous_new_version', function(version) {
        if (typeof version === 'undefined' || version !== background.unblock_youku.lastest_new_version) {
            background.set_storage('previous_new_version', background.unblock_youku.lastest_new_version);
        }
    });

    // $('div#version').html('Unblock Youku  ' + background.unblock_youku.version);

    // button actions
    $('input#input_lite').change(function() {
        console.log('to change mode to lite');
        background.change_mode('lite');
    });
    $('input#input_normal').change(function() {
        console.log('to change mode to normal');
        background.change_mode('normal');
    });
    $('input#input_redirect').change(function() {
        console.log('to change mode to redirect');
        background.change_mode('redirect');
    });

    var my_date = new Date();
    if (typeof localStorage.first_time === 'undefined') {
        localStorage.first_time = my_date.getTime();
    } else if (my_date.getTime() > parseInt(localStorage.first_time, 10) + 1000 * 60 * 60 * 24 * 3) {
        $('div#rating').show(); // delay 3 days for the rating div to show up, hahaha
    }
});

<|endoftext|>


<commit_before>var jayson = require(__dirname);
var utils = require('./utils');
var _ = require('lodash');

/**
 * @summary Constructor for a Jayson Method
 * @class Method
 * @param {Function} [handler] - Function to set as handler
 * @param {Object} [options]
 * @param {Function} [options.handler] - Same as separate handler
 * @param {Boolean} [options.collect=true] - Params to the handler are collected in one object
 * @param {Array|Object} [options.params] - Defines params that the handler accepts
 */
var Method = function(handler, options) {

  if(!(this instanceof Method)) {
    return new Method(handler, options);
  }

  // only got passed options
  if(_.isPlainObject(handler)) {
    options = handler;
    handler = null;
  }

  var defaults = {
    collect: true
  };

  options = options || {};

  this.options = utils.merge(defaults, options);
  this.handler = handler || options.handler;
};

module.exports = Method;

/**
 * @summary Returns the handler function associated with this method
 * @return {Function}
 */
Method.prototype.getHandler = function() {
  return this.handler;
};

/**
 * @summary Sets the handler function associated with this method
 * @param {Function} handler
 */
Method.prototype.setHandler = function(handler) {
  this.handler = handler;
};

/**
 * @summary Prepare parameters for the method handler
 * @private
 */
Method.prototype._getHandlerParams = function(params) {
  var options = this.options;
  var handler = this.getHandler();

  var isObjectParams = !_.isArray(params) && _.isObject(params) && params;
  var isArrayParams = _.isArray(params);

  if(options.collect) {
    // collect parameters in one argument to handler

    switch(true) {

      // handler always gets an array
      case options.params === Array:
        return isArrayParams ? params : _.toArray(params);

      // handler always gets an object
      case options.params === Object:
        return isObjectParams ? params : _.toPlainObject(params);

      // handler gets a list of defined properties that should always be set
      case _.isArray(options.params):
        var undefinedParams = _.object(_.zip(options.params, _.range(options.params.length).map(_.constant(undefined))));
        return _.extend(undefinedParams, _.pick(params, _.keys(params)));

      // handler gets a map of defined properties and their default values
      case _.isPlainObject(options.params):
        return _.extend({}, options.params, _.pick(params, _.keys(params)));

      // give params as is
      default:
        return params;

    }

  } else {
    // let the arguments pass to the handler as given

    if(isObjectParams) {
      // named parameters passed, take all parameters for handler except last (the callback)
      return _.initial(utils.getParameterNames(handler)).map(function(name) {
        return params[name];
      });
    }

    // regular parameters array passed
    return params;
  }
};

/**
 * @summary Executes this method in the context of a server
 * @param {Server} server
 * @param {Array|Object} requestParams
 * @param {Function} callback
 */
Method.prototype.execute = function(server, requestParams, callback) {
  var options = this.options;
  var handler = this.getHandler();
  var params = this._getHandlerParams(requestParams);

  if(options.collect) {
    return handler.call(server, params, callback);
  }

  // compare without the callback
  if(handler.length !== (params.length + 1)) {
    callback(server.error(jayson.Server.errors.INVALID_PARAMS));
    return;
  }

  return handler.apply(server, _.flatten([params, callback]));
};
<commit_msg>Allow for empty params when collect: false is used.<commit_after>var jayson = require(__dirname);
var utils = require('./utils');
var _ = require('lodash');

/**
 * @summary Constructor for a Jayson Method
 * @class Method
 * @param {Function} [handler] - Function to set as handler
 * @param {Object} [options]
 * @param {Function} [options.handler] - Same as separate handler
 * @param {Boolean} [options.collect=true] - Params to the handler are collected in one object
 * @param {Array|Object} [options.params] - Defines params that the handler accepts
 */
var Method = function(handler, options) {

  if(!(this instanceof Method)) {
    return new Method(handler, options);
  }

  // only got passed options
  if(_.isPlainObject(handler)) {
    options = handler;
    handler = null;
  }

  var defaults = {
    collect: true
  };

  options = options || {};

  this.options = utils.merge(defaults, options);
  this.handler = handler || options.handler;
};

module.exports = Method;

/**
 * @summary Returns the handler function associated with this method
 * @return {Function}
 */
Method.prototype.getHandler = function() {
  return this.handler;
};

/**
 * @summary Sets the handler function associated with this method
 * @param {Function} handler
 */
Method.prototype.setHandler = function(handler) {
  this.handler = handler;
};

/**
 * @summary Prepare parameters for the method handler
 * @private
 */
Method.prototype._getHandlerParams = function(params) {
  var options = this.options;
  var handler = this.getHandler();

  var isObjectParams = !_.isArray(params) && _.isObject(params) && params;
  var isArrayParams = _.isArray(params);

  if(options.collect) {
    // collect parameters in one argument to handler

    switch(true) {

      // handler always gets an array
      case options.params === Array:
        return isArrayParams ? params : _.toArray(params);

      // handler always gets an object
      case options.params === Object:
        return isObjectParams ? params : _.toPlainObject(params);

      // handler gets a list of defined properties that should always be set
      case _.isArray(options.params):
        var undefinedParams = _.object(_.zip(options.params, _.range(options.params.length).map(_.constant(undefined))));
        return _.extend(undefinedParams, _.pick(params, _.keys(params)));

      // handler gets a map of defined properties and their default values
      case _.isPlainObject(options.params):
        return _.extend({}, options.params, _.pick(params, _.keys(params)));

      // give params as is
      default:
        return params;

    }

  } else {
    // let the arguments pass to the handler as given

    if(isObjectParams) {
      // named parameters passed, take all parameters for handler except last (the callback)
      return _.initial(utils.getParameterNames(handler)).map(function(name) {
        return params[name];
      });
    }

    // regular parameters array passed
    return params;
  }
};

/**
 * @summary Executes this method in the context of a server
 * @param {Server} server
 * @param {Array|Object} requestParams
 * @param {Function} callback
 */
Method.prototype.execute = function(server, requestParams, callback) {
  var options = this.options;
  var handler = this.getHandler();
  var params = this._getHandlerParams(requestParams);

  if(options.collect) {
    return handler.call(server, params, callback);
  }

  // Params is optional according to the JSON-RPC 2.0 spec so if it doesnt
  // exist create an empty array.
  if (!params) {
    params = [];
  }

  // compare without the callback
  if(handler.length !== (params.length + 1)) {
    callback(server.error(jayson.Server.errors.INVALID_PARAMS));
    return;
  }

  return handler.apply(server, _.flatten([params, callback]));
};
<|endoftext|>


<commit_before>import {
  DEFAULT_OPTIONS,
} from './constants';

import isCharUpperCase from './utils/isCharUpperCase';
import hiraganaToKatakana from './utils/hiraganaToKatakana';
import {
  getRomajiToKanaTree,
  IME_MODE_MAP,
  USE_OBSOLETE_KANA_MAP,
} from './romajiToKanaMap';
import { applyMapping } from './kanaMappingUtils';

/**
 * Convert [Romaji](https://en.wikipedia.org/wiki/Romaji) to [Kana](https://en.wikipedia.org/wiki/Kana), lowercase text will result in [Hiragana](https://en.wikipedia.org/wiki/Hiragana) and uppercase text will result in [Katakana](https://en.wikipedia.org/wiki/Katakana).
 * @param  {String} [input=''] text
 * @param  {DefaultOptions} [options=defaultOptions]
 * @return {String} converted text
 * @example
 * toKana('onaji BUTTSUUJI')
 * // => 'おなじ ブッツウジ'
 * toKana('ONAJI buttsuuji')
 * // => 'オナジ ぶっつうじ'
 * toKana('座禅‘zazen’スタイル')
 * // => '座禅「ざぜん」スタイル'
 * toKana('batsuge-mu')
 * // => 'ばつげーむ'
 * toKana('!?.:/,~-‘’“”[](){}') // Punctuation conversion
 * // => '！？。：・、〜ー「」『』［］（）｛｝'
 * toKana('we', { useObsoleteKana: true })
 * // => 'ゑ'
 */
export function toKana(input = '', options = {}) {
  // just throw away the substring index information and just concatenate all the kana
  return splitIntoKana(input, options).map((kanaToken) => kanaToken[2]).join('');
}

export function splitIntoKana(input = '', options = {}) {
  const config = Object.assign({}, DEFAULT_OPTIONS, options);

  let map = getRomajiToKanaTree();
  map = config.IMEMode? IME_MODE_MAP(map): map;
  map = config.useObsoleteKana? USE_OBSOLETE_KANA_MAP(map): map;
  map = config.customKanaMapping(map);

  const result = applyMapping(input.toLowerCase(), map, !config.IMEMode);

  return result.map((element) => {
    const [start, end, kan] = element;
    if (kan === null) {
      // we encountered the last chunk, that should not be converted yet
      return [start, end, input.slice(start)];
    }
    if (!config.ignoreCase && isCharUpperCase(input[start])) {
      return [start, end, hiraganaToKatakana(kan)];
    }
    return element;
  });
}

export default toKana;
<commit_msg>Add post processing to toKana<commit_after>import {
  DEFAULT_OPTIONS,
} from './constants';
import {
  getKanaPostProcessing,
  getRomajiToKanaTree,
  IME_MODE_MAP,
  USE_OBSOLETE_KANA_MAP,
} from './romajiToKanaMap';
import { applyMapping } from './kanaMappingUtils';

/**
 * Convert [Romaji](https://en.wikipedia.org/wiki/Romaji) to [Kana](https://en.wikipedia.org/wiki/Kana), lowercase text will result in [Hiragana](https://en.wikipedia.org/wiki/Hiragana) and uppercase text will result in [Katakana](https://en.wikipedia.org/wiki/Katakana).
 * @param  {String} [input=''] text
 * @param  {DefaultOptions} [options=defaultOptions]
 * @return {String} converted text
 * @example
 * toKana('onaji BUTTSUUJI')
 * // => 'おなじ ブッツウジ'
 * toKana('ONAJI buttsuuji')
 * // => 'オナジ ぶっつうじ'
 * toKana('座禅‘zazen’スタイル')
 * // => '座禅「ざぜん」スタイル'
 * toKana('batsuge-mu')
 * // => 'ばつげーむ'
 * toKana('!?.:/,~-‘’“”[](){}') // Punctuation conversion
 * // => '！？。：・、〜ー「」『』［］（）｛｝'
 * toKana('we', { useObsoleteKana: true })
 * // => 'ゑ'
 */
export function toKana(input = '', options = {}) {
  // just throw away the substring index information and just concatenate all the kana
  return splitIntoKana(input, options).map((kanaToken) => kanaToken[2]).join('');
}

export function splitIntoKana(input = '', options = {}) {
  const config = Object.assign({}, DEFAULT_OPTIONS, options);
  const kanaPostProcessing = getKanaPostProcessing(config);

  let map = getRomajiToKanaTree(config);
  map = config.IMEMode? IME_MODE_MAP(map): map;
  map = config.useObsoleteKana? USE_OBSOLETE_KANA_MAP(map): map;
  map = config.customKanaMapping(map);

  const mappingResult = applyMapping(input.toLowerCase(), map, !config.IMEMode);
  let result = kanaPostProcessing([input, mappingResult]);
  result = config.customKanaPostProcessing(result);

  return result[1];
}

export default toKana;
<|endoftext|>


<commit_before>import _ from 'lodash';
import { octokatHelper } from './factory';

export var hasVote = function (str) {

  var lines = str.split('\n');

  var found = false;

  lines.forEach(function (line) {
    if (found) return;
    var isBlockQuote = line.match(/^\s*>/);
    if (isBlockQuote) return;
    found = found || line.match(/(^|[\W\b])(\+1|:\+1:|:thumbsup:|\uD83D\uDC4D)([\W\b]|$)/g);
    // TODO: Ignore +1's within quotation marks?
  });

  return found;
};

var withComments = function (comments) {
  getVoteCountForComments(comments);
};

var mapCommentsHaveVotes = function (comments) {
  return comments.map(c => {
    c.hasVote = hasVote(c.body);
    return c;
  });
};

var getVoteCountForComments = function (comments) {

  if (! comments) return 0;

  var voteCount = 0;

  var alreadyUsers = {};

  comments = mapCommentsHaveVotes(comments);

  comments.forEach(c => {

    var login = c.user.login;

    if (alreadyUsers[login]) {
      return;
    }

    if (c.hasVote) {
      alreadyUsers[login] = true;
      voteCount += 1;
    }
  });

  return voteCount;
};

export var showRepo = function (owner, repo, each, callback) {

  var merged = {};

  var merge = function (results) {
    if (! results) return results;
    var keyed = _.chain(results)
      .filter(r => r.issue)
      .groupBy(r => r.issue.number)
      .map(r => r[0])
      .value();
    _.extend(merged, keyed);
    return _.values(merged);
  };

  if (! octokatHelper()) return;

  octokatHelper().getIssuesThenComments(
    owner,
    repo,
    function (err, results, cancel) {
      each(err, mapResultsToRows(merge(results)), cancel);
    },
    function (err, results, cancel, issue) {
      each(err, mapResultsToRows(merge(results)), cancel, issue);
    },
    function (err, results, cancel, issue, comments) {
      each(err, mapResultsToRows(merge(results)), cancel, issue, comments);
    },
    (err, results, cancel) => {
      callback(err, mapResultsToRows(merge(results)), cancel);
    }
  );

};

var mapResultsToRows = function (results) {

  if (! results) return;

  results.forEach(function (result) {
    var voteCount = 0;
    if (result.comments) {
      voteCount = getVoteCountForComments(result.comments);
    }
    result.voteCount = voteCount;
  });

  results = _.sortBy(results, function (result) {
    return -1 * result.voteCount;
  });

  var components = [];

  var rows = [];

  results.forEach(function (result) {
    var issue = result.issue;
    var voteCount = result.voteCount;

    // var ratio = voteCount / issue.comments;

    rows.push({
      number: issue.number || '',
      title: issue.title || '',
      htmlUrl: issue.htmlUrl || '',
      owner: issue.owner || '',
      repo: issue.repo || '',
      voteCount: voteCount || ''
      // comments: issue.comments||'',
      // ratio:ratio ||''
    });
  });

  return rows;
};
<commit_msg>Export helper methods to fix issue route.<commit_after>import _ from 'lodash';
import { octokatHelper } from './factory';

export var hasVote = function (str) {

  var lines = str.split('\n');

  var found = false;

  lines.forEach(function (line) {
    if (found) return;
    var isBlockQuote = line.match(/^\s*>/);
    if (isBlockQuote) return;
    found = found || line.match(/(^|[\W\b])(\+1|:\+1:|:thumbsup:|\uD83D\uDC4D)([\W\b]|$)/g);
    // TODO: Ignore +1's within quotation marks?
  });

  return found;
};

export var mapCommentsHaveVotes = function (comments) {
  return comments.map(c => {
    c.hasVote = hasVote(c.body);
    return c;
  });
};

export var getVoteCountForComments = function (comments) {

  if (! comments) return 0;

  var voteCount = 0;

  var alreadyUsers = {};

  comments = mapCommentsHaveVotes(comments);

  comments.forEach(c => {

    var login = c.user.login;

    if (alreadyUsers[login]) {
      return;
    }

    if (c.hasVote) {
      alreadyUsers[login] = true;
      voteCount += 1;
    }
  });

  return voteCount;
};

export var showRepo = function (owner, repo, each, callback) {

  var merged = {};

  var merge = function (results) {
    if (! results) return results;
    var keyed = _.chain(results)
      .filter(r => r.issue)
      .groupBy(r => r.issue.number)
      .map(r => r[0])
      .value();
    _.extend(merged, keyed);
    return _.values(merged);
  };

  if (! octokatHelper()) return;

  octokatHelper().getIssuesThenComments(
    owner,
    repo,
    function (err, results, cancel) {
      each(err, mapResultsToRows(merge(results)), cancel);
    },
    function (err, results, cancel, issue) {
      each(err, mapResultsToRows(merge(results)), cancel, issue);
    },
    function (err, results, cancel, issue, comments) {
      each(err, mapResultsToRows(merge(results)), cancel, issue, comments);
    },
    (err, results, cancel) => {
      callback(err, mapResultsToRows(merge(results)), cancel);
    }
  );

};

export var mapResultsToRows = function (results) {

  if (! results) return;

  results.forEach(function (result) {
    var voteCount = 0;
    if (result.comments) {
      voteCount = getVoteCountForComments(result.comments);
    }
    result.voteCount = voteCount;
  });

  results = _.sortBy(results, function (result) {
    return -1 * result.voteCount;
  });

  var components = [];

  var rows = [];

  results.forEach(function (result) {
    var issue = result.issue;
    var voteCount = result.voteCount;

    // var ratio = voteCount / issue.comments;

    rows.push({
      number: issue.number || '',
      title: issue.title || '',
      htmlUrl: issue.htmlUrl || '',
      owner: issue.owner || '',
      repo: issue.repo || '',
      voteCount: voteCount || ''
      // comments: issue.comments||'',
      // ratio:ratio ||''
    });
  });

  return rows;
};
<|endoftext|>


<commit_before>define([
	'goo/math/Vector3',
	'goo/scripts/ScriptUtils',
	'goo/math/MathUtils'
], function(
	Vector3,
	ScriptUtils,
	MathUtils
) {
	'use strict';

	/**
	 * @class
	 */
	function AxisAlignedCamControlScript() {

		function setup(params, env) {
			setupMouseControls(params, env);
			env.axis		= new Vector3(0, 0, 1);
			env.upAxis		= new Vector3(0, 1, 0);
			setView(params, env, params.view);
			//env.currentView = params.view;
			env.targetAxis	= new Vector3(0, 0, 1);
			env.lookAtPoint	= new Vector3(0, 0, 0);
			env.distance	= params.distance;
			env.smoothness	= Math.pow(MathUtils.clamp(params.smoothness, 0, 1), 0.3);
			env.axisAlignedDirty = true;
		}

		function setView(params, env, view){
			if(env.currentView === view){
				return;
			}
			env.currentView = view;
			switch(view){
				case 'XY':
					env.axis.setd(0, 0, 1);
					env.upAxis.setd(0, 1, 0);
					break;
				case 'ZY':
					env.axis.setd(1, 0, 0);
					env.upAxis.setd(0, 1, 0);
					break;
			}
			env.axisAlignedDirty = true;
		}

		function update(params, env) {
			if(params.view != env.currentView){
				env.axisAlignedDirty = true;
			}
			if (!env.axisAlignedDirty) {
				return;
			}
			var entity = env.entity;
			var transform = entity.transformComponent.transform;

			var cameraPosition = Vector3.add(env.lookAtPoint, Vector3.mul(env.axis, env.distance));
			transform.translation.set(cameraPosition);
			transform.lookAt(env.lookAtPoint, env.upAxis);
			entity.transformComponent.setUpdated();

			env.axisAlignedDirty = false;

			//var delta = MathUtils.lerp(env.smoothness, 1, env.world.tpf);
			//size = env.size = MathUtils.lerp(delta, size, targetSize);
			//camera.setFrustum(1, AxisAlignedCamControlScript.LARGE_NUMBER, -size, size, size, -size, 1);
			/*if(Math.abs(targetSize-size) < 0.00001){
				env.twoDimDirty = false;
			} else {
				env.twoDimDirty = true;
			}*/
		}

		// Removes all listeners
		function cleanup(params, env) {
			for (var event in env.listeners) {
				env.domElement.removeEventListener(event, env.listeners[event]);
			}
		}

		// Attaches the needed mouse event listeners
		function setupMouseControls(params, env) {
			// Define listeners
			var listeners = env.listeners = {
			};

			// Attach listeners
			for (var event in listeners) {
				env.domElement.addEventListener(event, listeners[event]);
			}
		}

		return {
			setup: setup,
			update: update,
			cleanup: cleanup
		};
	}

	/**
	 * @static
	 * @type {Object}
	 */
	AxisAlignedCamControlScript.externals = {
		name: 'AxisAlignedCamControlScript',
		description: 'Aligns a camera along an axis, and enables switching between them.',
		parameters: [{
			key: 'whenUsed',
			'default': true,
			type: 'boolean'
		},{
			key: 'distance',
			name: 'Distance',
			type: 'float',
			description:'Camera distance from lookat point',
			control: 'slider',
			'default': 1e3,
			min: 1,
			max: 1e5
		},{
			key: 'view',
			type:'string',
			'default': 'XY',
			control:'select',
			options: ['XY', 'ZY']
		}]
	};

	return AxisAlignedCamControlScript;
});<commit_msg>minor fix<commit_after>define([
	'goo/math/Vector3',
	'goo/scripts/ScriptUtils',
	'goo/math/MathUtils'
], function(
	Vector3,
	ScriptUtils,
	MathUtils
) {
	'use strict';

	/**
	 * @class
	 */
	function AxisAlignedCamControlScript() {

		function setup(params, env) {
			setupMouseControls(params, env);
			env.axis		= new Vector3(0, 0, 1);
			env.upAxis		= new Vector3(0, 1, 0);
			setView(params, env, params.view);
			//env.currentView = params.view;
			env.targetAxis	= new Vector3(0, 0, 1);
			env.lookAtPoint	= new Vector3(0, 0, 0);
			env.distance	= params.distance;
			env.smoothness	= Math.pow(MathUtils.clamp(params.smoothness, 0, 1), 0.3);
			env.axisAlignedDirty = true;
		}

		function setView(params, env, view){
			if(env.currentView === view){
				return;
			}
			env.currentView = view;
			switch(view){
				case 'XY':
					env.axis.setd(0, 0, 1);
					env.upAxis.setd(0, 1, 0);
					break;
				case 'ZY':
					env.axis.setd(1, 0, 0);
					env.upAxis.setd(0, 1, 0);
					break;
			}
			env.axisAlignedDirty = true;
		}

		function update(params, env) {
			if(params.view !== env.currentView){
				env.axisAlignedDirty = true;
			}
			if (!env.axisAlignedDirty) {
				return;
			}
			var entity = env.entity;
			var transform = entity.transformComponent.transform;

			var cameraPosition = Vector3.add(env.lookAtPoint, Vector3.mul(env.axis, env.distance));
			transform.translation.set(cameraPosition);
			transform.lookAt(env.lookAtPoint, env.upAxis);
			entity.transformComponent.setUpdated();

			env.axisAlignedDirty = false;

			//var delta = MathUtils.lerp(env.smoothness, 1, env.world.tpf);
			//size = env.size = MathUtils.lerp(delta, size, targetSize);
			//camera.setFrustum(1, AxisAlignedCamControlScript.LARGE_NUMBER, -size, size, size, -size, 1);
			/*if(Math.abs(targetSize-size) < 0.00001){
				env.twoDimDirty = false;
			} else {
				env.twoDimDirty = true;
			}*/
		}

		// Removes all listeners
		function cleanup(params, env) {
			for (var event in env.listeners) {
				env.domElement.removeEventListener(event, env.listeners[event]);
			}
		}

		// Attaches the needed mouse event listeners
		function setupMouseControls(params, env) {
			// Define listeners
			var listeners = env.listeners = {
			};

			// Attach listeners
			for (var event in listeners) {
				env.domElement.addEventListener(event, listeners[event]);
			}
		}

		return {
			setup: setup,
			update: update,
			cleanup: cleanup
		};
	}

	/**
	 * @static
	 * @type {Object}
	 */
	AxisAlignedCamControlScript.externals = {
		name: 'AxisAlignedCamControlScript',
		description: 'Aligns a camera along an axis, and enables switching between them.',
		parameters: [{
			key: 'whenUsed',
			'default': true,
			type: 'boolean'
		},{
			key: 'distance',
			name: 'Distance',
			type: 'float',
			description:'Camera distance from lookat point',
			control: 'slider',
			'default': 1e3,
			min: 1,
			max: 1e5
		},{
			key: 'view',
			type:'string',
			'default': 'XY',
			control:'select',
			options: ['XY', 'ZY']
		}]
	};

	return AxisAlignedCamControlScript;
});<|endoftext|>


<commit_before>'use strict'

var d3 = require('d3')
d3.hexbin = require('../lib/d3-plugins/hexbin')
<commit_msg>drop unused file<commit_after><|endoftext|>