<commit_before>define [
  'underscore'
  'backbone'
  'cs!collections/content'
  'cs!models/content/module'
  'cs!mixins/loadable'
  'cs!gh-book/toc-node'
  'cs!gh-book/toc-pointer-node'
  'cs!gh-book/utils'
], (_, Backbone, allContent, Module, loadable, TocNode, TocPointerNode, Utils) ->

  return class BookModel extends (TocNode.extend loadable)
    mediaType: 'application/vnd.org.cnx.collection'
    accept: [Module::mediaType, TocNode::mediaType]

    # Used to fetch/save the content.
    # TODO: move the URL pattern to a separate file so it is configurable
    url: () -> "/content/#{@id}"

    initialize: (options) ->

      # For TocNode, let it know this is the root
      super {root:@}

      # TODO: Refactor references to @navModel
      # Used by gh-book because the OPF and navigation document are separate
      @navModel = @

      # Contains all items in the ToC (including internal nodes like "Chapter 3")
      @tocNodes = new Backbone.Collection()
      @tocNodes.add @

      # Use the `parse:true` option instead of `loading:true` because
      # Backbone sets this option when a model is being parsed.
      # This way we can ignore firing events when Backbone is parsing as well as
      # when we are internally updating models.
      setNavModel = (options) =>
        if not options.doNotReparse
          options.doNotReparse = true
          @navModel.set 'body', @_serializeNavModel(), options

      @tocNodes.on 'add', (model, collection, options) =>
        if not options.doNotReparse
          # Keep track of local changes if there is a remote conflict
          @_localNavAdded[model.id] = model

      # If a node was added-to/removed-from a TocNode ensure it is/is-not in the set of `tocNodes`
      # TODO: This may be redundant and may be able to be removed
      @tocNodes.on 'tree:add',    (model, collection, options) => @tocNodes.add model, options
      @tocNodes.on 'tree:remove', (model, collection, options) => @tocNodes.remove model, options

      # When a title changes on one of the nodes in the ToC:
      #
      # 1. remember the change
      # 2. try to autosave
      # 3. if a remote conflict occurse the remembered change will be replayed (see `onReloaded`)
      @tocNodes.on 'change:title', (model, value, options) =>
        return if not model.previousAttributes()['title'] # skip if we are parsing the file
        return if @ == model # Ignore if changing the OPF title
        # the `change:title` event "trickles up" through the nodes (probably should not)
        # so only save once.
        if @_localTitlesChanged[model.id] != value
          @_localTitlesChanged[model.id] = value
          @_save()

      @getChildren().on 'add remove tree:change tree:add tree:remove', (model, collection, options) =>
        setNavModel(options)
      @getChildren().on 'change reset', (collection, options) =>
        # HACK: `?` is because `inherits/container.add` calls `trigger('change')`
        setNavModel(options)

      # These store the added items since last successful save.
      # If this file was remotely updated then, when resolving conflicts,
      # these items will be added back into the newly-updated OPF manifest
      @_localNavAdded = {}
      @_localTitlesChanged = {}


    _loadComplex: (fetchPromise) ->
      # OPF subclass will add an event listener on `@navModel.on 'change:body', ...`
      # but since the navModel is @ we can jsut listen now
      # TODO: This could also be implemented in the constructor as `@navModel?.on ...`
      @on 'change:body', (model, value, options) =>
        return if options.parse
        @_parseNavModel(@get('body'))
      return fetchPromise

    # Called on "autosave".
    # Only save the navModel and new files (and all the OPF files).
    # Delay the save and if more than one thing changed during SAVE_DELAY
    # only save once.
    #
    # Reason for SAVE_DELAY: a "move" is 2 operations, `remove` followed by `add`
    _save: ->
      clearTimeout(@_savingTimeout)
      @_savingTimeout = setTimeout (() =>
        allContent.save(@navModel, false, true) # include-resources, include-new-files
        delete @_savingTimeout
      ), SAVE_DELAY

    # This specifies the mediaType of new Toc entries that do not exist in allContent yet.
    # It is used when the GitHub book Navigation file is updated before the OPF file is.
    # It is unused by `atc`.
    # Used in `_parseNavModel`.
    newModelMediaType: Module::mediaType
    # This method is given a Toc entry and the title in the Toc.
    # Optionally, the method can assign the title to the Toc entry.
    # It is unused by `atc`.
    # Used in `_parseNavModel`.
    onParseNavEntry: (model, title) -> # no-op


    parse: (json) ->
      body = json.body
      # Remove the body since we parse it manually
      json = _.omit('body')
      @_parseNavModel(body)
      return json


    _parseNavModel: (bodyHtml) ->
      $body = $(bodyHtml)
      $body = $('<div></div>').append $body
<commit_msg>reduce number of reparses when dropping content<commit_after>define [
  'underscore'
  'backbone'
  'cs!collections/content'
  'cs!models/content/module'
  'cs!mixins/loadable'
  'cs!gh-book/toc-node'
  'cs!gh-book/toc-pointer-node'
  'cs!gh-book/utils'
], (_, Backbone, allContent, Module, loadable, TocNode, TocPointerNode, Utils) ->

  return class BookModel extends (TocNode.extend loadable)
    mediaType: 'application/vnd.org.cnx.collection'
    accept: [Module::mediaType, TocNode::mediaType]

    # Used to fetch/save the content.
    # TODO: move the URL pattern to a separate file so it is configurable
    url: () -> "/content/#{@id}"

    initialize: (options) ->

      # For TocNode, let it know this is the root
      super {root:@}

      # TODO: Refactor references to @navModel
      # Used by gh-book because the OPF and navigation document are separate
      @navModel = @

      # Contains all items in the ToC (including internal nodes like "Chapter 3")
      @tocNodes = new Backbone.Collection()
      @tocNodes.add @

      # Use the `parse:true` option instead of `loading:true` because
      # Backbone sets this option when a model is being parsed.
      # This way we can ignore firing events when Backbone is parsing as well as
      # when we are internally updating models.
      setNavModel = (options) =>
        if not options.doNotReparse
          options.doNotReparse = true
          @navModel.set 'body', @_serializeNavModel(), options

      @tocNodes.on 'add', (model, collection, options) =>
        if not options.doNotReparse
          # Keep track of local changes if there is a remote conflict
          @_localNavAdded[model.id] = model

      # If a node was added-to/removed-from a TocNode ensure it is/is-not in the set of `tocNodes`
      # TODO: This may be redundant and may be able to be removed
      @tocNodes.on 'tree:add',    (model, collection, options) => @tocNodes.add model, options
      @tocNodes.on 'tree:remove', (model, collection, options) => @tocNodes.remove model, options

      # When a title changes on one of the nodes in the ToC:
      #
      # 1. remember the change
      # 2. try to autosave
      # 3. if a remote conflict occurse the remembered change will be replayed (see `onReloaded`)
      @tocNodes.on 'change:title', (model, value, options) =>
        return if not model.previousAttributes()['title'] # skip if we are parsing the file
        return if @ == model # Ignore if changing the OPF title
        # the `change:title` event "trickles up" through the nodes (probably should not)
        # so only save once.
        if @_localTitlesChanged[model.id] != value
          @_localTitlesChanged[model.id] = value
          @_save()

      @getChildren().on 'add remove tree:change tree:add tree:remove', (model, collection, options) =>
        setNavModel(options)
      @getChildren().on 'change reset', (collection, options) =>
        # HACK: `?` is because `inherits/container.add` calls `trigger('change')`
        setNavModel(options)

      # These store the added items since last successful save.
      # If this file was remotely updated then, when resolving conflicts,
      # these items will be added back into the newly-updated OPF manifest
      @_localNavAdded = {}
      @_localTitlesChanged = {}


    _loadComplex: (fetchPromise) ->
      # OPF subclass will add an event listener on `@navModel.on 'change:body', ...`
      # but since the navModel is @ we can jsut listen now
      # TODO: This could also be implemented in the constructor as `@navModel?.on ...`
      @on 'change:body', (model, value, options) =>
        return if options.parse or options.doNotReparse
        @_parseNavModel(@get('body'))
      return fetchPromise

    # This specifies the mediaType of new Toc entries that do not exist in allContent yet.
    # It is used when the GitHub book Navigation file is updated before the OPF file is.
    # It is unused by `atc`.
    # Used in `_parseNavModel`.
    newModelMediaType: Module::mediaType
    # This method is given a Toc entry and the title in the Toc.
    # Optionally, the method can assign the title to the Toc entry.
    # It is unused by `atc`.
    # Used in `_parseNavModel`.
    onParseNavEntry: (model, title) -> # no-op


    parse: (json) ->
      body = json.body
      # Remove the body since we parse it manually
      json = _.omit('body')
      @_parseNavModel(body)
      return json


    _parseNavModel: (bodyHtml) ->
      $body = $(bodyHtml)
      $body = $('<div></div>').append $body

{'fraction_non_alphanumeric': 0.10061773636268453, 'fraction_numerical': 0.0009423097057899697, 'mean_word_length': 2.8070944599442007, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6626988', 'n_tokens_mistral': 2854, 'n_tokens_neox': 2745, 'n_words': 1032}