diff options
author | rmacqueen <rorymacqueen@gmail.com> | 2013-10-24 13:41:50 -0700 |
---|---|---|
committer | rmacqueen <rorymacqueen@gmail.com> | 2013-10-24 13:41:50 -0700 |
commit | 636837cb710951486ef6514265c78ccd5c773c27 (patch) | |
tree | 2183917b6d73de7242e04a72a293f2f67ea23830 | |
parent | 2a1de6f30520afe5b7461bca58d90b57b70f5527 (diff) | |
parent | 758af67e4d3c48c530f11995351d5640231ed914 (diff) |
Merge pull request #370 from endlessm/issues/367
Issues/367
-rw-r--r-- | test/Makefile.am | 4 | ||||
-rw-r--r-- | test/wikipedia/models/testArticleModel.js | 71 | ||||
-rw-r--r-- | test/wikipedia/models/testCategoryModel.js | 76 | ||||
-rw-r--r-- | test/wikipedia/models/testDomainWikiModel.js | 141 | ||||
-rw-r--r-- | wikipedia/ArticleList.js | 19 | ||||
-rw-r--r-- | wikipedia/PrebuiltFrontPage.js | 6 | ||||
-rw-r--r-- | wikipedia/models/article_model.js | 83 | ||||
-rw-r--r-- | wikipedia/models/category_model.js | 98 | ||||
-rw-r--r-- | wikipedia/models/domain_wiki_model.js | 133 | ||||
-rw-r--r-- | wikipedia/presenters/domain_wiki_presenter.js | 100 | ||||
-rw-r--r-- | wikipedia/views/domain_wiki_view.js | 26 | ||||
-rw-r--r-- | wikipedia/widgets/category_selector_view.js | 22 |
12 files changed, 661 insertions, 118 deletions
diff --git a/test/Makefile.am b/test/Makefile.am index 60a4057..53ff6ff 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -35,6 +35,9 @@ javascript_tests = \ test/tools/eos-run-test/sanitycheck.js \ test/webhelper/testTranslate.js \ test/webhelper/testWebActions.js \ + test/wikipedia/models/testCategoryModel.js \ + test/wikipedia/models/testArticleModel.js \ + test/wikipedia/models/testDomainWikiModel.js \ $(NULL) EXTRA_DIST += $(javascript_tests) @@ -46,6 +49,7 @@ TESTS = \ TEST_EXTENSIONS = .js JS_LOG_COMPILER = tools/eos-run-test AM_JS_LOG_FLAGS = \ + --include-path=$(top_srcdir)/wikipedia \ --include-path=$(top_srcdir)/webhelper \ $(NULL) LOG_COMPILER = gtester diff --git a/test/wikipedia/models/testArticleModel.js b/test/wikipedia/models/testArticleModel.js new file mode 100644 index 0000000..bea76dc --- /dev/null +++ b/test/wikipedia/models/testArticleModel.js @@ -0,0 +1,71 @@ +const ArticleModel = imports.models.article_model; + +let mockJsonData = { + title: 'Article Title', + url: 'file:///', + categories: [ + 'Category One', + 'Category Two' + ] +}; + +function _assertCategoryListHasIds(categoryList, idList) { + assertEquals(idList.length, categoryList.length); + idList.forEach(function (id) { + assertTrue(categoryList.some(function (actualId) { + return actualId == id; + })); + }); +} + +function testNewModelFromJson() { + let model = ArticleModel.newFromJson(mockJsonData); + assertTrue(model instanceof ArticleModel.ArticleModel); + assertEquals('Article Title', model.title); + assertEquals('file:///', model.uri); + _assertCategoryListHasIds(model.getCategories(), + ['Category One', 'Category Two']); +} + +function testNewWithProperties() { + let model = new ArticleModel.ArticleModel({ + title: 'Article Title', + uri: 'file:///' + }); + assertEquals('Article Title', model.title); + assertEquals('file:///', model.uri); + assertEquals(0, model.getCategories().length); +} + +function testSetAndGetCategories() { + let model = new ArticleModel.ArticleModel(); + let expectedCategories = ['One', 'Two', 'Three']; + model.setCategories(expectedCategories); + _assertCategoryListHasIds(model.getCategories(), expectedCategories); +} + +function testSetCategoriesWipesPreviousCategories() { + let model = new ArticleModel.ArticleModel(); + let firstCategories = ['One', 'Two', 'Three']; + model.setCategories(firstCategories); + let expectedCategories = ['A', 'B', 'C', 'D']; + model.setCategories(expectedCategories); + _assertCategoryListHasIds(model.getCategories(), expectedCategories); +} + +function testAddAndGetCategories() { + let model = new ArticleModel.ArticleModel(); + model.addCategory('One'); + model.addCategory('Two'); + model.addCategory('Three'); + _assertCategoryListHasIds(model.getCategories(), ['One', 'Two', 'Three']); +} + +function testHasCategories() { + let model = new ArticleModel.ArticleModel(); + let expectedCategories = ['One', 'Two', 'Three']; + model.setCategories(expectedCategories); + expectedCategories.forEach(function (id) { + assertTrue(model.hasCategory(id)); + }); +} diff --git a/test/wikipedia/models/testCategoryModel.js b/test/wikipedia/models/testCategoryModel.js new file mode 100644 index 0000000..9314e84 --- /dev/null +++ b/test/wikipedia/models/testCategoryModel.js @@ -0,0 +1,76 @@ +const CategoryModel = imports.models.category_model; + +let mockJsonData = { + category_name: 'Category Name', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///thumb.jpg', + is_main_category: false, + subcategories: [ 'Category Two' ] +}; + +function testNewModelFromJson() { + let model = CategoryModel.newFromJson(mockJsonData); + assertTrue(model instanceof CategoryModel.CategoryModel); + assertEquals('Category Name', model.id); + assertEquals(0, model.getSubcategories().length); +} + +function testNewWithProperties() { + let model = new CategoryModel.CategoryModel({ + id: 'id', + title: 'title', + description: 'description', + image_uri: 'image-uri', + image_thumbnail_uri: 'image-thumbnail-uri', + is_main_category: true, + has_articles: true + }); + assertEquals('id', model.id); + assertEquals('title', model.title); + assertEquals('description', model.description); + assertEquals('image-uri', model.image_uri); + assertEquals('image-thumbnail-uri', model.image_thumbnail_uri); + assertEquals(true, model.is_main_category); + assertEquals(true, model.has_articles); + + model.has_articles = false; + assertEquals(false, model.has_articles); +} + +function testGetSubcategoriesEmpty() { + let model = new CategoryModel.CategoryModel(); + assertEquals(0, model.getSubcategories().length); +} + +function _assertCategoryListContainsCategoryIds(categoryList, idList) { + assertEquals(idList.length, categoryList.length); + idList.forEach(function (id) { + assertTrue(categoryList.some(function (categoryModel) { + return categoryModel.id == id; + })); + }); +} + +function testAddAndGetSubcategories() { + let model1 = new CategoryModel.CategoryModel({ id: 'Category One' }); + let model2 = new CategoryModel.CategoryModel({ id: 'Category Two' }); + let model3 = new CategoryModel.CategoryModel({ id: 'Category Three' }); + model1.addSubcategory(model2); + model1.addSubcategory(model3); + + let categories = model1.getSubcategories(); + _assertCategoryListContainsCategoryIds(categories, + ['Category Two', 'Category Three']); +} + +function testAddSubcategoryDoesNothingForDuplicate() { + let model1 = new CategoryModel.CategoryModel({ id: 'Category One' }); + let model2 = new CategoryModel.CategoryModel({ id: 'Category Two' }); + let model3 = new CategoryModel.CategoryModel({ id: 'Category Two' }); + model1.addSubcategory(model2); + model1.addSubcategory(model3); + + let categories = model1.getSubcategories(); + _assertCategoryListContainsCategoryIds(categories, ['Category Two']); +} diff --git a/test/wikipedia/models/testDomainWikiModel.js b/test/wikipedia/models/testDomainWikiModel.js new file mode 100644 index 0000000..f476b16 --- /dev/null +++ b/test/wikipedia/models/testDomainWikiModel.js @@ -0,0 +1,141 @@ +const DomainWikiModel = imports.models.domain_wiki_model; + +let model; + +let mockJsonData = { + categories: [ + { + category_name: 'Main Category', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: true, + subcategories: [ + 'Category One', + 'Category Two' + ] + }, + { + category_name: 'Category One', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: false, + subcategories: [] + }, + { + category_name: 'Category Two', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: false, + subcategories: [ + 'Category Three' + ] + }, + { + category_name: 'Category Three', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: false, + subcategories: [] + }, + ], + articles: [ + { + title: 'Article One', + url: 'file:///article1.html', + categories: [ + 'Category One' + ] + }, + { + title: 'Article Two', + url: 'file:///article2.html', + categories: [ + 'Category One', + 'Category Two' + ] + }, + { + title: 'Article Three', + url: 'file:///article3.html', + categories: [ + 'Category Two' + ] + } + ] +}; + +function setUp() { + model = new DomainWikiModel.DomainWikiModel(); +} + +function _assertArticleListContainsArticleTitles(articleList, titleList) { + assertEquals(titleList.length, articleList.length); + titleList.forEach(function (title) { + assertTrue(articleList.some(function (articleModel) { + return articleModel.title == title; + })); + }); +} + +function testGetArticlesReturnsAllArticles() { + model.loadFromJson(mockJsonData); + let articles = model.getArticles(); + _assertArticleListContainsArticleTitles(articles, + [ 'Article One', 'Article Two', 'Article Three' ]); +} + +function testGetArticlesForCategoryWithArticles() { + model.loadFromJson(mockJsonData); + let articles = model.getArticlesForCategory('Category One'); + _assertArticleListContainsArticleTitles(articles, + [ 'Article One', 'Article Two' ]); +} + +function testGetArticlesForCategoryWithoutArticles() { + model.loadFromJson(mockJsonData); + assertEquals(0, model.getArticlesForCategory('Main Category').length); +} + +function testGetArticlesForCategoryWithNonexistentId() { + assertEquals(0, model.getArticlesForCategory('Nonexistent').length); +} + +function testCategoryHasArticlesReturnsTrue() { + model.loadFromJson(mockJsonData); + assertTrue(model._getCategoryHasArticles('Category Two')); +} + +function testCategoryHasArticlesReturnsFalse() { + model.loadFromJson(mockJsonData); + assertFalse(model._getCategoryHasArticles('Category Three')); +} + +function testCategoryHasArticlesWithNonexistentId() { + assertFalse(model._getCategoryHasArticles('Nonexistent')); +} + +function testGetCategory() { + model.loadFromJson(mockJsonData); + let category = model.getCategory('Category One'); + assertEquals('CategoryModel', category.__name__); + assertEquals('Category One', category.title); +} + +function testGetNonexistentCategory() { + assertUndefined(model.getCategory('Nonexistent')); +} + +function testGetMainCategory() { + model.loadFromJson(mockJsonData); + let category = model.getMainCategory(); + assertTrue(category.__name__ == 'CategoryModel'); + assertEquals('Main Category', category.title); +} + +function testGetUnsetMainCategory() { + assertNull(model.getMainCategory()); +} diff --git a/wikipedia/ArticleList.js b/wikipedia/ArticleList.js index 0b145b8..7ce2639 100644 --- a/wikipedia/ArticleList.js +++ b/wikipedia/ArticleList.js @@ -14,7 +14,7 @@ const ArticleList = new Lang.Class({ Signals: { 'article-chosen': { - param_types: [GObject.TYPE_STRING, GObject.TYPE_INT] + param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING] } }, @@ -34,7 +34,13 @@ const ArticleList = new Lang.Class({ this.add(this._grid); }, - // Takes a list of dictionaries with keys 'title' and 'uri' + /** + * Method: setArticles + * Set articles to display in this widget + * + * Parameters: + * articles - An array of <ArticleModels> + */ setArticles: function(articles) { // Remove all existing article links this._grid.get_children().forEach(function(element, index, obj) { @@ -42,12 +48,13 @@ const ArticleList = new Lang.Class({ }, this); // Create new ones - articles.forEach(function(title, index, obj) { - let button = new ListTextButton.ListTextButton(HOVER_ARROW_URI, title, {hexpand:true}); + articles.forEach(function (article) { + let button = new ListTextButton.ListTextButton(HOVER_ARROW_URI, + article.title, { hexpand: true }); button.connect('clicked', Lang.bind(this, function() { - this.emit('article-chosen', title, index); + this.emit('article-chosen', article.title, article.uri); })); - + this._grid.add(button); }, this); } diff --git a/wikipedia/PrebuiltFrontPage.js b/wikipedia/PrebuiltFrontPage.js index 73f915e..828cbf3 100644 --- a/wikipedia/PrebuiltFrontPage.js +++ b/wikipedia/PrebuiltFrontPage.js @@ -14,7 +14,7 @@ const PrebuiltFrontPage = new Lang.Class({ Extends: Gtk.Grid, Signals: { 'category-chosen': { - param_types: [GObject.TYPE_STRING, GObject.TYPE_INT] + param_types: [GObject.TYPE_STRING] } }, @@ -55,7 +55,7 @@ const PrebuiltFrontPage = new Lang.Class({ }, // Proxy signal - _onCategoryChosen: function(widget, title, index) { - this.emit('category-chosen', title, index); + _onCategoryChosen: function(widget, categoryId) { + this.emit('category-chosen', categoryId); } });
\ No newline at end of file diff --git a/wikipedia/models/article_model.js b/wikipedia/models/article_model.js index ac6ea49..5c6cc15 100644 --- a/wikipedia/models/article_model.js +++ b/wikipedia/models/article_model.js @@ -2,21 +2,98 @@ const Endless = imports.gi.Endless; const GObject = imports.gi.GObject; const Lang = imports.lang; +GObject.ParamFlags.READWRITE = GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE; const ArticleModel = new Lang.Class({ Name: "ArticleModel", Extends: GObject.Object, Properties: { 'title': GObject.ParamSpec.string('title', 'Article Title', 'Human Readable Article Title', - GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ""), 'uri': GObject.ParamSpec.string('uri', 'Article URI', 'Title URI as stored in wikipedia database', - GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, "") }, _init: function(params) { + this._categoryList = []; this.parent(params); + }, + + /** + * Method: setCategories + * Give this article a list of categories or tags + * + * Parameters: + * categoryList - an array of strings signifying category IDs + * + * Makes this article's categories the list signified by _categoryList_, + * wiping out any categories that were previously set. + */ + setCategories: function (categoryList) { + this._categoryList = categoryList; + }, + + /** + * Method: addCategory + * Attach a category or tag to this article + * + * Parameters: + * categoryId - a string signifying a category ID + * + * Tags this article with _categoryID_. + * Does nothing if this article is already tagged with that ID. + */ + addCategory: function (categoryId) { + if (!this.hasCategory(categoryId)) + this._categoryList.push(categoryId); + }, + + /** + * Method: getCategories + * List of this article's categories or tags + * + * Returns: + * An array of strings signifying category IDs. + */ + getCategories: function () { + return this._categoryList; + }, + + /** + * Method: hasCategory + * Whether this article is tagged with a particular ID + * + * Parameters: + * categoryId - a string signifying a category ID + * + * Returns: + * true if this article is tagged with _categoryId_, false if not. + */ + hasCategory: function (categoryId) { + return this._categoryList.indexOf(categoryId) != -1; } -});
\ No newline at end of file +}); + +/** + * Function: newFromJson + * Construct a new <ArticleModel> from data exported by the CMS + * + * Parameters: + * json - a category object created by parsing the JSON file + * + * Returns: + * A newly created <ArticleModel>. + * + * See <DomainWikiModel.loadFromJson> for the structure of the JSON object. + */ +function newFromJson(json) { + let retval = new ArticleModel({ + title: json['title'], + uri: json['url'] + }); + retval.setCategories(json['categories']); + return retval; +} diff --git a/wikipedia/models/category_model.js b/wikipedia/models/category_model.js index 8a1c1ff..a17c9ac 100644 --- a/wikipedia/models/category_model.js +++ b/wikipedia/models/category_model.js @@ -2,39 +2,113 @@ const Endless = imports.gi.Endless; const GObject = imports.gi.GObject; const Lang = imports.lang; -// Local libraries -const ArticleModel = imports.wikipedia.models.article_model; +GObject.ParamFlags.READWRITE = GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE; const CategoryModel = new Lang.Class({ Name: "CategoryModel", Extends: GObject.Object, Properties: { + /** + * Property: id + * String for referring to this category internally + * + * Can generally be equal to <CategoryModel.title>, though that is not + * required. + */ + 'id': GObject.ParamSpec.string('id', 'ID string', + 'String for referring to this category internally', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + ''), 'description': GObject.ParamSpec.string('description', 'Category Description', 'This is the text that the user reads on the category page.', - GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ""), 'title': GObject.ParamSpec.string('title', 'Category Name', 'This is the name that is displayed on the front page and as the title on the category page.', - GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ""), 'image-uri': GObject.ParamSpec.string('image-uri', 'Category Image URI', 'Path to image for this category in the GResource', - GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ""), 'image-thumbnail-uri': GObject.ParamSpec.string('image-thumbnail-uri', 'Category Thumbnail Image URI', 'Path to thumbnail image for this category in the GResource', - GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ""), 'is-main-category': GObject.ParamSpec.boolean('is-main-category', 'Is Main Category boolean', 'Flag denoting whether this category is the main category for this app', - GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT, + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, false), + /** + * Property: has_articles + * Whether this category contains articles + * + * This property is not computed in any way, but is set by the library + * model when loading from a JSON file. + * This value is used to determine whether a category should be + * clickable, for example. + */ + 'has-articles': GObject.ParamSpec.boolean('has-articles', + 'Has articles', + 'Indicates whether category has any articles under it', + GObject.ParamFlags.READWRITE, + false) }, _init: function(params) { + this._subcategories = {}; this.parent(params); }, - addArticles: function(articles) { - this._articles = articles; + /** + * Method: addSubcategory + * Add a subcategory to this model + * + * Parameters: + * modelObj - another <CategoryModel> that is to be a subcategory of this + * one. + * + * Does nothing if this category already has a subcategory with _modelObj_'s + * ID. + */ + addSubcategory: function (modelObj) { + if (!this._subcategories.hasOwnProperty(modelObj.id)) { + this._subcategories[modelObj.id] = modelObj; + } }, - getArticles: function() { - return this._articles; + /** + * Method: getSubcategories + * List this model's subcategories + * + * Returns: + * An array of <CategoryModels> representing this category's + * subcategories, or an empty array if there are none. + */ + getSubcategories: function () { + let retval = []; + for (let id in this._subcategories) { + retval.push(this._subcategories[id]); + } + return retval; } -});
\ No newline at end of file +}); + +/** + * Function: newFromJson + * Construct a new <CategoryModel> from data exported by the CMS + * + * Parameters: + * json - a category object created by parsing the JSON file + * + * Returns: + * A newly created <CategoryModel> with no subcategories. + * + * See <DomainWikiModel.loadFromJson> for the structure of the JSON object. + */ +function newFromJson(json) { + let retval = new CategoryModel({ + id: json['category_name'], + description: json['content_text'], + title: json['category_name'], + image_uri: json['image_file'], + image_thumbnail_uri: json['image_thumb_uri'], + is_main_category: json['is_main_category'] + }); + return retval; +} diff --git a/wikipedia/models/domain_wiki_model.js b/wikipedia/models/domain_wiki_model.js index cb9071b..b5ced4e 100644 --- a/wikipedia/models/domain_wiki_model.js +++ b/wikipedia/models/domain_wiki_model.js @@ -5,19 +5,86 @@ const GObject = imports.gi.GObject; const Lang = imports.lang; // Local libraries +const ArticleModel = imports.wikipedia.models.article_model; const CategoryModel = imports.wikipedia.models.category_model; -const Utils = imports.wikipedia.utils; const DomainWikiModel = new Lang.Class({ Name: "DomainWikiModel", Extends: GObject.Object, - //params should have the image-uri for the app's image, and the application name. _init: function(params) { + this._articles = []; + this._mainCategory = null; + this._categories = {}; this.parent(params); }, + /** + * Method: loadFromJson + * Populate the model from the CMS's exported JSON file + * + * Parameters: + * json - an object created by parsing the JSON file + * + * Call this once, when creating the model, to populate it using the JSON + * file defining the categories and articles. + * The JSON file adheres to the following format: + * + * > <MAIN> = + * > { + * > "categories": [ <CATEGORY>, <CATEGORY>, ... ], + * > "articles": [ <ARTICLE>, <ARTICLE>, ... ] + * > } + * > <CATEGORY> = + * > { + * > "category_name": <string>, + * > "content_text": <string>, + * > "image_file": <string>, + * > "image_thumb_uri": <string>, + * > "is_main_category": <boolean>, + * > "subcategories": [ <string>, <string>, ... ] + * > } + * + * "subcategories" is a list of "category_name" strings from other + * categories. + * "subcategories" can be empty. + * "is_main_category" will probably disappear from a future version. + * + * > <ARTICLE> = + * > { + * > "title": <string>, + * > "url": <string>, + * > "categories": [ <string>, <string>, ... ], + * > } + * + * "categories" is a list of "category_name" strings from the categories this + * article is associated with. + * "categories" can be empty, but generally should not. + */ + loadFromJson: function (json) { + // Load list of articles + this._articles = json['articles'].map(function (article) { + return ArticleModel.newFromJson(article); + }); + + // First create flat list of category models, indexed by ID + json['categories'].forEach(function (category) { + let modelObj = CategoryModel.newFromJson(category); + if (modelObj.is_main_category) + this._mainCategory = modelObj; + this._categories[modelObj.id] = modelObj; + modelObj.has_articles = this._getCategoryHasArticles(modelObj.id); + }, this); + // Create links between all the category models in a tree + json['categories'].forEach(function (category) { + category['subcategories'].forEach(function(subcatId) { + this._categories[category['category_name']].addSubcategory( + this._categories[subcatId]); + }, this); + }, this); + }, + setLinkedArticles:function(articles){ this._linked_articles = articles; }, @@ -26,17 +93,63 @@ const DomainWikiModel = new Lang.Class({ return this._linked_articles; }, - //categories should be a list of category models, already populated with article models. - addCategories: function(categories){ - this._categories = categories; + /** + * Method: getArticles + * Articles available in this library + */ + getArticles: function () { + return this._articles; + }, + + /** + * Method: getArticlesForCategory + * Articles belonging to a category in this library + * + * Parameters: + * id - The string ID of a category + * + * Returns: + * An array of <ArticleModels> belonging to the category signified by _id_ + * or an empty array if there were none or _id_ was not found + */ + getArticlesForCategory: function (id) { + return this._articles.filter(function (article) { + return article.getCategories().indexOf(id) != -1; + }); + }, + + // A faster version of getArticlesForCategory() that just returns true if + // there were any articles in this category + _getCategoryHasArticles: function (id) { + return this._articles.some(function (article) { + return article.getCategories().indexOf(id) != -1; + }); }, - getArticlesForCategoryIndex: function(index){ - let category = this.getCategories()[index]; - return category.getArticles(); + /** + * Method: getCategory + * Category corresponding to a string ID + * + * Parameters: + * id - The string ID of a category + * + * Returns: + * A <CategoryModel> that corresponds to _id_, or undefined if _id_ was + * not found. + */ + getCategory: function (id) { + return this._categories[id]; }, - getCategories: function() { - return this._categories; + /** + * Method: getMainCategory + * Category marked as "main" for this library + * + * Returns: + * A <CategoryModel> that has been marked as the "main" category, or null + * if the main category has not been set yet. + */ + getMainCategory: function () { + return this._mainCategory; } });
\ No newline at end of file diff --git a/wikipedia/presenters/domain_wiki_presenter.js b/wikipedia/presenters/domain_wiki_presenter.js index 3e34e1e..05af35e 100644 --- a/wikipedia/presenters/domain_wiki_presenter.js +++ b/wikipedia/presenters/domain_wiki_presenter.js @@ -1,3 +1,4 @@ +const Endless = imports.gi.Endless; const Lang = imports.lang; const GObject = imports.gi.GObject; @@ -25,98 +26,63 @@ const DomainWikiPresenter = new Lang.Class({ Extends: GObject.Object, _init: function(model, view, app_filename, linked_articles_filename) { - this._domain_wiki_model = model; - this._domain_wiki_view = view; - this._domain_wiki_view.set_presenter(this) - this._domain_wiki_view.connect('category-chosen', Lang.bind(this, this._onCategoryClicked)); - this._domain_wiki_view.connect('article-chosen', Lang.bind(this, this._onArticleClicked)); + this._model = model; + this._view = view; + this._view.set_presenter(this); + this._view.connect('category-chosen', + Lang.bind(this, this._onCategoryClicked)); + this._view.connect('article-chosen', + Lang.bind(this, this._onArticleClicked)); this.initAppInfoFromJsonFile(app_filename); this.initPageRankFromJsonFile(linked_articles_filename); - this._domain_wiki_view.set_categories(this._domain_wiki_model.getCategories()); + let firstLevel = this._model.getMainCategory().getSubcategories(); + firstLevel.push(this._model.getMainCategory()); + this._view.set_categories(firstLevel); - let linked_articles = this._domain_wiki_model.getLinkedArticles(); + let linked_articles = this._model.getLinkedArticles(); let to_show = linked_articles["app_articles"].concat(linked_articles["extra_linked_articles"]); - this._domain_wiki_view.set_showable_links(to_show); - }, - - initArticleModels: function(articles) { - let _articles = new Array(); - for(let i = 0; i < articles.length; i++) { - let humanTitle = articles[i].title; - let wikipediaURL = articles[i].url; - let newArticle = new ArticleModel.ArticleModel({ title: humanTitle, uri: wikipediaURL}); - _articles.push(newArticle); - } - return _articles; + this._view.set_showable_links(to_show); }, initPageRankFromJsonFile: function(filename){ let articles = JSON.parse(Utils.load_file_from_resource(filename)); - this._domain_wiki_model.setLinkedArticles(articles); + this._model.setLinkedArticles(articles); }, initAppInfoFromJsonFile: function(filename) { let app_content = JSON.parse(Utils.load_file_from_resource(filename)); - this._domain_wiki_view.set_lang(_pathnameToLanguage(filename)); - let categories = app_content['categories']; - let cat_length = categories.length - let category_models = new Array(); - for(let i = 0; i < cat_length; i++){ - let category = categories[i]; - let categoryModel = this.initCategory(category); - let articles = category['articles']; - let articleModels = []; - if(!(articles.length == 0)) { - //if the category has no articles, then we cannot initialize them. - //This happens if the main category isn't clickable. - articleModels = this.initArticleModels(articles); - } - categoryModel.addArticles(articleModels); - category_models.push(categoryModel); - } - this._domain_wiki_model.addCategories(category_models); - }, - - initCategory: function(category){ - let image_uri = category['image_file']; - let image_thumbnail_uri = category['image_thumb_uri']; - let params = {description:category['content_text'], image_uri:image_uri, - image_thumbnail_uri:image_thumbnail_uri, title:category['category_name'], - is_main_category:category['is_main_category']}; - return new CategoryModel.CategoryModel(params); + this._model.loadFromJson(app_content); }, - _onCategoryClicked: function(page, title, index) { - this._current_category = index; - let category = this._domain_wiki_model.getCategories()[index]; - let articles = this._domain_wiki_model.getArticlesForCategoryIndex(index); - - let titles = new Array(); - for(let i = 0; i < articles.length; i++){ - titles.push(articles[i].title); - } + // Respond to the front page's 'category-clicked' signal by loading the + // articles belonging to that category and switching to the category page + _onCategoryClicked: function (page, categoryId) { + let newCategory = this._model.getCategory(categoryId); + let articles = this._model.getArticlesForCategory(categoryId); - this._domain_wiki_view.set_category_info(category, titles); + this._view.set_category_info(newCategory, articles); - this._domain_wiki_view.transition_page(Endless.PageManagerTransitionType.SLIDE_LEFT, 'category'); + this._view.transition_page(Endless.PageManagerTransitionType.SLIDE_LEFT, + 'category'); }, - _onArticleClicked: function(article_list, title, index) { - let articles = this._domain_wiki_model.getArticlesForCategoryIndex(this._current_category); - this._domain_wiki_view.set_article_info(articles[index]); - this._domain_wiki_view.transition_page(Endless.PageManagerTransitionType.SLIDE_LEFT, 'article'); - + // Respond to the category page's 'article-clicked' signal by loading that + // article and switching to the article page + _onArticleClicked: function (articleList, title, uri) { + this._view.set_article_info(title, uri); + this._view.transition_page(Endless.PageManagerTransitionType.SLIDE_LEFT, + 'article'); }, _onCategoryBackClicked: function(button) { - this._window.page_manager.transition_type = Endless.PageManagerTransitionType.SLIDE_RIGHT; - this._window.page_manager.visible_page_name = 'front'; + this._view.transition_page( + Endless.PageManagerTransitionType.SLIDE_RIGHT, 'front'); }, _onArticleBackClicked: function(button) { - this._window.page_manager.transition_type = Endless.PageManagerTransitionType.SLIDE_RIGHT; - this._window.page_manager.visible_page_name = 'category'; + this._view.transition_page( + Endless.PageManagerTransitionType.SLIDE_RIGHT, 'category'); } });
\ No newline at end of file diff --git a/wikipedia/views/domain_wiki_view.js b/wikipedia/views/domain_wiki_view.js index 3462702..bb68c7e 100644 --- a/wikipedia/views/domain_wiki_view.js +++ b/wikipedia/views/domain_wiki_view.js @@ -16,10 +16,10 @@ const DomainWikiView = new Lang.Class({ Extends: GObject.Object, Signals: { 'category-chosen': { - param_types: [GObject.TYPE_STRING, GObject.TYPE_INT] + param_types: [GObject.TYPE_STRING] }, 'article-chosen': { - param_types: [GObject.TYPE_STRING, GObject.TYPE_INT] + param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING] } }, @@ -177,10 +177,14 @@ const DomainWikiView = new Lang.Class({ this._article_back_button.label = category.title.toUpperCase(); }, - set_article_info: function(article){ + /** + * Method: set_article_info + * Proxy method to set the article displaying on the article page + */ + set_article_info: function (title, uri) { // Note: Must set article title first - this._article_view.article_title = article.title; - this._article_view.article_uri = article.uri; + this._article_view.article_title = title; + this._article_view.article_uri = uri; }, set_lang: function(lang) { @@ -200,12 +204,16 @@ const DomainWikiView = new Lang.Class({ this._article_view.setShowableLinks(linked_articles); }, - _onCategoryClicked: function(page, title, index) { - this.emit('category-chosen', title, index); + // Proxy signal, respond to front page's 'category-chosen' signal by + // emitting our own + _onCategoryClicked: function (page, categoryId) { + this.emit('category-chosen', categoryId); }, - _onArticleClicked: function(article_list, title, index) { - this.emit('article-chosen', title, index); + // Proxy signal, respond to category page's 'article-chosen' signal by + // emitting our own + _onArticleClicked: function (articleList, title, uri) { + this.emit('article-chosen', title, uri); }, _onCategoryBackClicked: function(button) { diff --git a/wikipedia/widgets/category_selector_view.js b/wikipedia/widgets/category_selector_view.js index 57188ea..674e563 100644 --- a/wikipedia/widgets/category_selector_view.js +++ b/wikipedia/widgets/category_selector_view.js @@ -13,7 +13,7 @@ const CategorySelectorView = new Lang.Class({ Extends: CategoryLayoutManager.CategoryLayoutManager, Signals: { 'category-chosen': { - param_types: [GObject.TYPE_STRING, GObject.TYPE_INT] + param_types: [GObject.TYPE_STRING] } }, @@ -24,20 +24,26 @@ const CategorySelectorView = new Lang.Class({ this.parent(props); }, - // Takes an array of dictionaries with keys 'title' and 'image_uri' + /** + * Method: setCategories + * Create buttons in this view for a list of categories to display + * + * Parameters: + * categories - An array of <CategoryModels> + * + */ setCategories: function(categories) { - categories.forEach(function(category, index, obj) { - let isClickable = !category.getArticles().length == 0; + categories.forEach(function (category) { let button = new CategoryButton.CategoryButton({ category_title: category.title, image_uri: category.image_thumbnail_uri, - clickable_category: isClickable, + clickable_category: category.has_articles, is_main_category: category.is_main_category, hexpand: !category.is_main_category }); - button.index = index; + button.id = category.id; // ID to return to when clicked //if the category has no articles, you shouldn't be able to click on it. - if(isClickable) { + if (category.has_articles) { button.connect('clicked', Lang.bind(this, this._onButtonClicked)); } @@ -46,6 +52,6 @@ const CategorySelectorView = new Lang.Class({ }, _onButtonClicked: function(button) { - this.emit('category-chosen', button.category_title, button.index); + this.emit('category-chosen', button.id); } });
\ No newline at end of file |