function openMarkConflictPopup(component_id, conflictMarks) {
    let gradeable_id = getGradeableId();
    let popup = $('#mark-conflict-popup');

    // Set the component title
    popup.find('.component-title').html(getComponentJQuery(component_id).attr('data-title'));

    // Prep the conflict marks
    prepConflictMarks(conflictMarks);

    // Generate the list of marks
    return renderConflictMarks(conflictMarks)
        .then(function (elements) {
            // Insert the rendered elements
            popup.find('.conflict-marks-container').html(elements);

            // Open the popup
            popup.show();

            // Setup the promise so that resolving the last mark will resolve the promise
            return new Promise(function (resolve, reject) {
                // Function to tag a mark as resolved and move to the next mark
                let resolveMark = function(mark_id) {
                    tagMarkConflictResolved(mark_id);
                    if(!anyUnresolvedConflicts()) {
                        popup.hide();
                        resolve();
                    }
                    else {
                        showNextConflict();
                    }
                };

                // In order for the event handlers to have the power to resolve the promise,
                //  they need to be established within the promise
                popup.find('.mark-resolve-dom .btn').click(function() {
                    let id = getConflictMarkIdFromDOMElement(this);
                    let mark = conflictMarks[id].domMark;

                    Promise.resolve()
                        .then(function() {
                            if (conflictMarks[id].local_deleted) {
                                return ajaxDeleteMark(gradeable_id, component_id ,id)
                                    .catch(function (err) {
                                        // Don't let this error hold up the whole operation
                                        alert('Could not delete mark: ' + err.message);
                                    });
                            }
                            else {
                                // If the mark was deleted from the server, but we want to keep our changes,
                                //  we need to re-add the mark
                                if (isMarkServerDeleted(id)) {
                                    return ajaxAddNewMark(gradeable_id, component_id, mark.title, mark.points, mark.publish)
                                        .then(function (data) {
                                            mark.id = data.mark_id;
                                        });
                                }
                                else {
                                    return ajaxSaveMark(gradeable_id, component_id, id, mark.title, mark.points, mark.publish);
                                }
                            }
                        })
                        .then(function() {
                            resolveMark(id);
                        })
                        .catch(function(err) {
                            console.error(err);
                            alert('Failed to resolve conflict! ' + err.message);
                        });
                });
                popup.find('.mark-resolve-old-server .btn').click(function() {
                    let id = getConflictMarkIdFromDOMElement(this);
                    let mark = conflictMarks[id].oldServerMark;
                    ajaxSaveMark(gradeable_id, component_id, id, mark.title, mark.points, mark.publish)
                        .then(function() {
                            resolveMark(id);
                        })
                        .catch(function(err) {
                            console.error(err);
                            alert('Failed to resolve conflict! ' + err.message);
                        });
                });
                popup.find('.mark-resolve-server .btn').click(function() {
                    // If we choose the server mark, we don't do anything
                    resolveMark(getConflictMarkIdFromDOMElement(this));
                });
            })
        });
}
/**
 * Prompts the user with an array of conflict marks so they can individually resolve them
 * @param {int} component_id
 * @param {{domMark, serverMark, oldServerMark, localDeleted}[]} conflictMarks
 * @return {Promise<Object>} Promise resolves with an array of marks to save indexed by mark id.
 *                              The mark will be null if it should be deleted
 */
Prompts the user with an array of conflict marks so they can individually resolve them
@param {int} component_id
@param {{domMark, serverMark, oldServerMark, localDeleted}[]} conflictMarks
@return {Promise} Promise resolves with an array of marks to save indexed by mark id.
The mark will be null if it should be deleted
Prompts the user with an array of conflict marks so they can individually resolve them @param { int } component_id @param {{ domMark serverMark oldServerMark localDeleted } [] } conflictMarks @return { Promise } Promise resolves with an array of marks to save indexed by mark id . The mark will be null if it should be deleted
function openMarkConflictPopup(component_id, conflictMarks) {
    let gradeable_id = getGradeableId();
    let popup = $('#mark-conflict-popup');
    popup.find('.component-title').html(getComponentJQuery(component_id).attr('data-title'));
    prepConflictMarks(conflictMarks);
    return renderConflictMarks(conflictMarks)
        .then(function (elements) {
            popup.find('.conflict-marks-container').html(elements);
            popup.show();
            return new Promise(function (resolve, reject) {
                let resolveMark = function(mark_id) {
                    tagMarkConflictResolved(mark_id);
                    if(!anyUnresolvedConflicts()) {
                        popup.hide();
                        resolve();
                    }
                    else {
                        showNextConflict();
                    }
                };
                popup.find('.mark-resolve-dom .btn').click(function() {
                    let id = getConflictMarkIdFromDOMElement(this);
                    let mark = conflictMarks[id].domMark;
                    Promise.resolve()
                        .then(function() {
                            if (conflictMarks[id].local_deleted) {
                                return ajaxDeleteMark(gradeable_id, component_id ,id)
                                    .catch(function (err) {
                                        alert('Could not delete mark: ' + err.message);
                                    });
                            }
                            else {
                                if (isMarkServerDeleted(id)) {
                                    return ajaxAddNewMark(gradeable_id, component_id, mark.title, mark.points, mark.publish)
                                        .then(function (data) {
                                            mark.id = data.mark_id;
                                        });
                                }
                                else {
                                    return ajaxSaveMark(gradeable_id, component_id, id, mark.title, mark.points, mark.publish);
                                }
                            }
                        })
                        .then(function() {
                            resolveMark(id);
                        })
                        .catch(function(err) {
                            console.error(err);
                            alert('Failed to resolve conflict! ' + err.message);
                        });
                });
                popup.find('.mark-resolve-old-server .btn').click(function() {
                    let id = getConflictMarkIdFromDOMElement(this);
                    let mark = conflictMarks[id].oldServerMark;
                    ajaxSaveMark(gradeable_id, component_id, id, mark.title, mark.points, mark.publish)
                        .then(function() {
                            resolveMark(id);
                        })
                        .catch(function(err) {
                            console.error(err);
                            alert('Failed to resolve conflict! ' + err.message);
                        });
                });
                popup.find('.mark-resolve-server .btn').click(function() {
                    resolveMark(getConflictMarkIdFromDOMElement(this));
                });
            })
        });
}
function openMarkConflictPopup ( component_id , conflictMarks ) { let gradeable_id = getGradeableId ( ) ; let popup = $ ( '#mark-conflict-popup' ) ; popup . find ( '.component-title' ) . html ( getComponentJQuery ( component_id ) . attr ( 'data-title' ) ) ; prepConflictMarks ( conflictMarks ) ; return renderConflictMarks ( conflictMarks ) . then ( function ( elements ) { popup . find ( '.conflict-marks-container' ) . html ( elements ) ; popup . show ( ) ; return new Promise ( function ( resolve , reject ) { let resolveMark = function ( mark_id ) { tagMarkConflictResolved ( mark_id ) ; if ( ! anyUnresolvedConflicts ( ) ) { popup . hide ( ) ; resolve ( ) ; } else { showNextConflict ( ) ; } } ; popup . find ( '.mark-resolve-dom .btn' ) . click ( function ( ) { let id = getConflictMarkIdFromDOMElement ( this ) ; let mark = conflictMarks [ id ] . domMark ; Promise . resolve ( ) . then ( function ( ) { if ( conflictMarks [ id ] . local_deleted ) { return ajaxDeleteMark ( gradeable_id , component_id , id ) . catch ( function ( err ) { alert ( 'Could not delete mark: ' + err . message ) ; } ) ; } else { if ( isMarkServerDeleted ( id ) ) { return ajaxAddNewMark ( gradeable_id , component_id , mark . title , mark . points , mark . publish ) . then ( function ( data ) { mark . id = data . mark_id ; } ) ; } else { return ajaxSaveMark ( gradeable_id , component_id , id , mark . title , mark . points , mark . publish ) ; } } } ) . then ( function ( ) { resolveMark ( id ) ; } ) . catch ( function ( err ) { console . error ( err ) ; alert ( 'Failed to resolve conflict! ' + err . message ) ; } ) ; } ) ; popup . find ( '.mark-resolve-old-server .btn' ) . click ( function ( ) { let id = getConflictMarkIdFromDOMElement ( this ) ; let mark = conflictMarks [ id ] . oldServerMark ; ajaxSaveMark ( gradeable_id , component_id , id , mark . title , mark . points , mark . publish ) . then ( function ( ) { resolveMark ( id ) ; } ) . catch ( function ( err ) { console . error ( err ) ; alert ( 'Failed to resolve conflict! ' + err . message ) ; } ) ; } ) ; popup . find ( '.mark-resolve-server .btn' ) . click ( function ( ) { resolveMark ( getConflictMarkIdFromDOMElement ( this ) ) ; } ) ; } ) } ) ; }
Prompts the user with an array of conflict marks so they can individually resolve them
 @param {int} component_id
 @param {{domMark, serverMark, oldServerMark, localDeleted}[]} conflictMarks
 @return {Promise<Object>} Promise resolves with an array of marks to save indexed by mark id.
Prompts the user with an array of conflict marks so they can individually resolve them @param { int } component_id @param {{ domMark serverMark oldServerMark localDeleted } [] } conflictMarks @return { Promise<Object > } Promise resolves with an array of marks to save indexed by mark id .
// Set the component title // Prep the conflict marks // Generate the list of marks // Insert the rendered elements // Open the popup // Setup the promise so that resolving the last mark will resolve the promise // Function to tag a mark as resolved and move to the next mark // In order for the event handlers to have the power to resolve the promise, //  they need to be established within the promise // Don't let this error hold up the whole operation // If the mark was deleted from the server, but we want to keep our changes, //  we need to re-add the mark // If we choose the server mark, we don't do anything