list.js

angular.module('homepage').
directive('list', function($compile, $timeout, RecursionHelper) {
    return {
        compile: ListCompile,
        controller: 'ListController',
        link: ListLinker,
        replace: true,
        restrict: 'E',
        scope: {
            list: '=',
            isDisabled: '='
        },
        templateUrl: 'components/feature-components/list/list.html'
    };

    function ListLinker(scope, element, attribute) {
        $timeout(function() {});
    }

    function ListCompile(element) {
        // Use the compile function from the RecursionHelper,
        // And return the linking function(s) which it returns
        return RecursionHelper.compile(element);
    }
}).
directive('listitem', function($compile, $timeout) {
    return {
        // attributes are passed into the isolate scope of the directive
        // and manipulated via the controller method passed into the isolate scope
        link: ListItemLinker,
        replace: true,
        restrict: 'E',
        scope: {
            list: '=',
            isDisabled: '=',
            itemindex: '=',
            setList: '&'
        },
        template: '<div class="listitem"><input class="input-field" type="text" ' +
            'ng-disabled="isDisabled" ng-model="list[itemindex.index].value"/></div>'
    };

    function ListItemLinker(scope, element, attribute) {
        element.on('keyup', function(event) {
            event.stopPropagation();
            var code = event.keyCode || event.which;
            var itemindex = scope.itemindex.index;
            if(code == 13) {
                scope.setList();
                $timeout(function() {
                    scope.$apply();
                });
            }
        });
    }
}).
controller('ListController', function($scope, ListHelper) {
    var list = [
        {
            value: ''
        }
    ];
    // initialize list only if creating a list
    if($scope.list === undefined) {
        $scope.list = angular.copy(list);
    }
    $scope.setList = function(itemindex) {
        var newitemindex = parseInt(itemindex) + 1;
        var itemvalue = $scope.list[itemindex].value;
        if(itemvalue !== undefined && typeof newitemindex == 'number') {
            var listsize = $scope.list.length;
            var item = {
                value: ''
            };
            var isStartOfList = ListHelper.isCreatingList(itemvalue);
            if(isStartOfList) {
                // If last item of the list add one item to list end, before appending list
                $scope.list.push(item);
                $scope.list[itemindex].list = angular.copy(list);
            }
            else {
                if(newitemindex == listsize){
                    $scope.list.push(item);
                }
                else {
                    // Insert item in between the array
                    // insert at new index, remove 0, item to be inserted
                    $scope.list.splice(newitemindex, 0, item);
                }
            }
        }
    };
}).
factory('ListHelper', function() {
    return {
        // Check if line ends with '::'
        isCreatingList: function(line) {
            line = line + '';
            line = line.trim();

            if(line.charAt(line.length - 1) == ':') {
                if(line.charAt(line.length - 2) == ':') {
                    return true;
                }
            }

            return false;
        }
    };
}).
factory('RecursionHelper', function($compile){
    return {
        /**
         * Manually compiles the element, fixing the recursion loop.
         * @param element
         * @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
         * @returns An object containing the linking functions.
         */
        compile: function(element, link){
            // Normalize the link parameter
            if(angular.isFunction(link)){
                link = { post: link };
            }

            // Break the recursion loop by removing the contents
            var contents = element.contents().remove();
            var compiledContents;
            return {
                pre: (link && link.pre) ? link.pre : null,
                /**
                 * Compiles and re-adds the contents
                 */
                post: function(scope, element){
                    // Compile the contents
                    if(!compiledContents){
                        compiledContents = $compile(contents);
                    }
                    // Re-add the compiled contents to the element
                    compiledContents(scope, function(clone){
                        element.append(clone);
                    });

                    // Call the post-linking function, if any
                    if(link && link.post){
                        link.post.apply(null, arguments);
                    }
                }
            };
        }
    };
});