Skip to content

Commit

Permalink
Proof of concept: Simple red & self link CSS entry point
Browse files Browse the repository at this point in the history
- Format red & self links using CSS only.
- Next steps:
    - store CSS & update it when linked pages are created or deleted
    - verify visual match with skin, use skin provided values instead of
      hardcoded styles
    - provide a JSON API for all / missing links as well for custom styling
    - get rid of ../ href prefix in Parsoid in subpages, use ./ and <base
      href> exclusively
  • Loading branch information
gwicke committed Sep 27, 2014
1 parent facf017 commit 6e3856b
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 19 deletions.
13 changes: 7 additions & 6 deletions doc/MediaWikiPageContent.md
Expand Up @@ -218,15 +218,16 @@ cache fragmentation for old content.
- red links
- [example query](http://en.wikipedia.org/w/api.php?action=query&prop=info&format=json&titles=Foo&generator=links&gpllimit=500)
- look for 'missing' in the result
- could / should save this, but will need cache invalidation
- will need to add pagelinks to job, currently only templatelinks
- should save this, but will need cache invalidation on article creation /
deletion using pagelinks (currently only templatelinks)
- self links https://bugzilla.wikimedia.org/show_bug.cgi?id=67486
- should be straightforward with CSS
- bad image list

### Possibly later
- page images not to render in MediaViewer (similar to image block list)
- stub thresholds (for some users, separately, pref)

### Needs pref info
- section edit links
- toc
- stub thresholds (for some users, separately, pref)

### Possibly later
- page images not to render in MediaViewer (similar to image block list)
79 changes: 76 additions & 3 deletions lib/filters/bucket/pagecontent.js
Expand Up @@ -153,12 +153,12 @@ PCBucket.prototype.getLatestFormat = function(restbase, req) {
action: 'query',
prop: 'revisions',
rvprop: 'ids',
titles: rp.key
titles: decodeURIComponent(rp.key)
}
})
.then(function(apiRes) {
if (apiRes.status === 200) {
var rev = apiRes.body[0].revisions[0];
var rev = apiRes.body.items[0].revisions[0];
return {
status: 302,
headers: {
Expand Down Expand Up @@ -303,7 +303,7 @@ PCBucket.prototype.getFormatRevision = function(restbase, req) {
})
.then(function(apiRes) {
if (apiRes.status === 200) {
var rev = apiRes.body[0].revisions[0];
var rev = apiRes.body.items[0].revisions[0];
var tid = rbUtil.tidFromDate(new Date(rev.timestamp));
req.uri = '/v1/' + rp.domain + '/' + rp.bucket + '.' + rp.format + '/' +
rp.key + '/' + tid;
Expand Down Expand Up @@ -389,6 +389,76 @@ PCBucket.prototype.getRevision = function(restbase, req) {
});
};

// XXX: save CSS
// prop=info&format=json&titles=Foo&generator=links&gpllimit=500)
PCBucket.prototype.getPageCSS = function(restbase, req) {
var rp = req.params;
function getLinkChunks(missing, gplcontinue) {
return restbase.get({
uri: '/v1/' + rp.domain + '/action/query',
query: {
prop: 'info',
titles: decodeURIComponent(rp.key),
generator: 'links',
gpllimit: 500,
gplcontinue: gplcontinue
}
})
.then(function(res) {
// Look for 'missing' entries
res.body.items.forEach(function(link) {
if (link.missing !== undefined) {
missing.push(link.title);
}
});
if (res.body.next) {
return getLinkChunks(missing, res.body.next.links.gplcontinue);
} else {
return missing;
}
});
}

// Title to href
function sanitize(bit) {
return bit
.replace(/ /g, '_')
.replace( /[%? \[\]#|<>]/g, function ( m ) {
return encodeURIComponent( m );
} )
// additional selector CSS escaping
.replace(/"/g, '\\"');
}

return getLinkChunks([]).then(function(missing) {
var css = '';
if (missing.length) {
// Red link CSS
var hrefs = missing.map(function(title) {
var bits = title.split('#', 1);
return './' + bits.map(sanitize).join('#');
});
css += hrefs.map(function(href) {
return 'a[href="' + href + '"]';
}).join(',\n');
css += ' { color: #BA0000; }\n';

}
// Self links
css += 'a[href="./' + sanitize(decodeURIComponent(rp.key)) + '"] {'
+ ' font-weight: bold; color: inherit;'
+ ' text-decoration: inherit; pointer-events: none;'
+ ' cursor: text; }\n';
return {
status: 200,
headers: {
'content-type': 'text/css'
},
body: css
};
});
};

module.exports = function(options) {
var bucket = new PCBucket(options);
// XXX: add docs
Expand All @@ -407,6 +477,9 @@ module.exports = function(options) {
'/v1/{domain}/{bucket}/{key}/': {
get: { request_handler: bucket.listItem.bind(bucket) }
},
'/v1/{domain}/{bucket}/{key}/css': {
get: { request_handler: bucket.getPageCSS.bind(bucket) }
},
'/v1/{domain}/{bucket}/{key}/{format}': {
get: { request_handler: bucket.getLatestFormat.bind(bucket) },
put: { request_handler: bucket.putLatestFormat.bind(bucket) }
Expand Down
14 changes: 10 additions & 4 deletions lib/filters/global/actions.js
Expand Up @@ -11,21 +11,27 @@ module.exports = {
request_handler: function(restbase, req) {
var rp = req.params;
req.uri = 'http://' + rp.domain + '/w/api.php';
req.body.action = 'query';
var body = req.body || req.query;
body.action = 'query';
// Always request json
req.body.format = 'json';
return restbase.post(req)
body.format = 'json';
return restbase[req.method](req)
.then(function(res) {
if (res.status !== 200) {
return res;
}
// Rewrite res.body
// XXX: Rethink!
var pages = res.body.query.pages;
var newBody = [];
Object.keys(pages).forEach(function(key) {
newBody.push(pages[key]);
});
res.body = newBody;
// XXX: Clean this up!
res.body = {
items: newBody,
next: res.body["query-continue"]
};
return res;
});
}
Expand Down
12 changes: 6 additions & 6 deletions lib/storage.js
Expand Up @@ -192,7 +192,7 @@ Storage.prototype.loadRegistry = function() {
var self = this;
var store = function(req) {
return self.stores.default({}, req);
}
};
// XXX: Retrieve the global config using the default revisioned blob
// bucket & backend
var sysDomain = this.config.sysdomain;
Expand All @@ -211,7 +211,7 @@ Storage.prototype.loadRegistry = function() {
method: 'put',
uri: '/v1/' + sysDomain + '/domains',
body: domainRegistrySchema
})
});
}
})
// check if the 'table' registry exists
Expand Down Expand Up @@ -347,7 +347,7 @@ Storage.prototype.listDomains = function (restbase, req) {
items: Object.keys(this.registry)
}
});
}
};

Storage.prototype.putDomain = function (restbase, req) {
var self = this;
Expand Down Expand Up @@ -512,11 +512,11 @@ Storage.prototype.listBuckets = function (restbase, req) {
Storage.prototype.getBucket = function (restbase, req) {
var domain = req.params.domain.toLowerCase();
var bucket = req.params.bucket;
var data = this.registry[domain].tables[bucket];
if (data) {
var domainData = this.registry[domain];
if (domainData && domainData.tables[bucket]) {
return Promise.resolve({
status: 200,
body: data
body: domainData.tables[bucket]
});
} else {
return Promise.resolve({ status: 404 });
Expand Down

0 comments on commit 6e3856b

Please sign in to comment.