summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrmacqueen <rorymacqueen@gmail.com>2013-10-24 13:41:50 -0700
committerrmacqueen <rorymacqueen@gmail.com>2013-10-24 13:41:50 -0700
commit636837cb710951486ef6514265c78ccd5c773c27 (patch)
tree2183917b6d73de7242e04a72a293f2f67ea23830
parent2a1de6f30520afe5b7461bca58d90b57b70f5527 (diff)
parent758af67e4d3c48c530f11995351d5640231ed914 (diff)
Merge pull request #370 from endlessm/issues/367
Issues/367
-rw-r--r--test/Makefile.am4
-rw-r--r--test/wikipedia/models/testArticleModel.js71
-rw-r--r--test/wikipedia/models/testCategoryModel.js76
-rw-r--r--test/wikipedia/models/testDomainWikiModel.js141
-rw-r--r--wikipedia/ArticleList.js19
-rw-r--r--wikipedia/PrebuiltFrontPage.js6
-rw-r--r--wikipedia/models/article_model.js83
-rw-r--r--wikipedia/models/category_model.js98
-rw-r--r--wikipedia/models/domain_wiki_model.js133
-rw-r--r--wikipedia/presenters/domain_wiki_presenter.js100
-rw-r--r--wikipedia/views/domain_wiki_view.js26
-rw-r--r--wikipedia/widgets/category_selector_view.js22
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