StylishThemes / GitHub-Dark

:octocat: Dark GitHub style
https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/github-dark.user.css
BSD 2-Clause "Simplified" License
9.67k stars 655 forks source link

Styler / Chrome Extension #251

Closed simov closed 9 years ago

simov commented 9 years ago

:wave:

I just wanted to share my personal Chrome extension that I use to inject styles into pages - https://github.com/simov/styler. I mentioned it a few times in previous issues here, and since I recently cleaned it a bit I thought it may be useful to someone (I use it for about 3 years now).

I have the extension folder placed inside my Dropbox folder, so whenever I change something it gets synced. I don't use chrome.storage.sync because the configuration is loaded from the file system anyway. Also currently there is no auto update of styles feature, but that can be added easily (check out the source code if you want, it's pretty simple).

I have one question though: do you know how can I detect the @-moz-document wrappers and probably disable them? I'm currently commenting them out by hand.

Mottie commented 9 years ago

Hey @simov!

Nice work! It would be even better if your extension allowed regular expressions to include domains. You can see the extent at which this style covers in the @-moz-document rule:

@-moz-document
  regexp("^https?://((gist|guides|help|raw|status|developer)\.)?github\.com((?!generated_pages\/preview).)*$"),
  domain("render.githubusercontent.com"),
  domain("raw.githubusercontent.com") {

As for removing that rule, you could use grunt to build you a customized style, or copy the regular expression that is used in the Gruntfile.js to remove that wrapper - it does not remove the closing curly bracket (}).

simov commented 9 years ago

Thanks, that makes sense. I guess the Stylish extension is reading the domains from there and then removes the line @-moz-document line before injecting the style. I think the closing curly bracket won't break anything if it stays there.

I can easily get the domains regex from that line, or add support for regex domains in the configuration (I was thinking about that actually). Anyway, thanks for the explanation, feel free to close this issue, I just wanted to share the extension. I'll probably add some support for regex domains and most definitely will remove that line dynamically.

silverwind commented 9 years ago

Nice! Suggestion: Instead of pulling from a dropbox, allow the user to define styles by providing URLs. Github does support that and the same could probably be done for styles hosted on userstyles.org. Then, you could do a job that pulls updates to the styles daily.

Also, @document is actually on a standard track and it's a bit of a shame that the Chrome devs haven't implemented it after all these years.

If you want to do it properly, you'd have to use something like PostCSS to parse the CSS into an AST and then apply each document rule (there can be multiple and they can even be nested in theory) to the specified domains/domain regexes. Thought for 99% of all styles, you will probably get away with your method :)

Related: https://github.com/JasonBarnabe/stylish-chrome/issues/31

simov commented 9 years ago

@silverwind, I can probably use IndexedDB to store the file contents (the rest is easy). Thanks for the suggestion.

About the @document rules - I know there may be multiple instances of them, I'll read through that thread :+1:

silverwind commented 9 years ago

PostCSS and rework are probably both suited to this task. Let me know if you get anywhere, Stylish for Chrome could use something like this too :)

simov commented 9 years ago

I'm removing the @-moz-document rule for now https://github.com/simov/styler/commit/3522e93686c4b2ff2a9a61abcd322740d1234144, which was my biggest concern ATM (I'll probably capture those rules and parse them separately after that).

I tried using rework which seems to be nice, but without some API to manage the generated AST data structure it's pretty useless (that's from quick 5 min look). Of course I can create some underscore nastiness to get what I want, but still it feels like overkill for my use case.

Now to explain a little bit about this extension, why I'm using it, and why I'm sharing it here: up until I found this theme for GitHub I used the extension only for injecting quick fixes for various sites that I browse frequently. Naturally what I wanted was to use Sublime Text for editing, not only because I don't want to reinvent the wheel (which is fun sometimes), but because Sublime will be always far superior than any other in browser editor (it was that way 3 years ago).

The reason why the extension is not published in Chrome Store is because I don't consider it end user friendly. In order to make it end user friendly I'll have to use IndexedDB for storing the file contents. The files in the extension's folder are not supposed to be changed in a Packed extension, that's why I'm using it as Unpacked one, and I have a Sublime Project set for that folder.

Now when the style is an actual theme that comes for URL like:

https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/github-dark.css
https://userstyles.org/styles/62289/black-youtube-by-panos.css

It makes sense to have automatic update, because I'm not going to edit these theme files directly, I can add additional file after them. But then again if I want to update the file automatically, I have to store it somewhere, and that's IndexedDB (btw Stylish uses it too :) And that seems like an overkill for me too.

So in short: this extension is for injecting quick fixes, not necessarily a full themes published somewhere. Btw I used Stylish for adding quick fixes before I made this extension, and using its built-in editor never felt right for me :)

silverwind commented 9 years ago

Yeah, not being able to edit styles directly in an external editor is one of my pain points with Stylish too (for Firefox in my case). On Firefox, Stylish stores themes in a SQLite by the way. I'm a heavy Greasemonkey user too, and it's so nice to edit in my own editor and just F5 :heart_eyes:.

I'll have to play with these AST parsers too sometimes. It might be a more robust replacement than our whacky string replacements we do in the Gruntfile. I was under the impression it's just parse -> modify -> stringify for rework, but PostCSS is probably a more complete solution anyways.

simov commented 9 years ago

re: rework

parse -> modify -> stringify

It is but before you can modify, you have to filter/find what you are searching for. After I parsed the github-dark theme I was looking at some endless objects with arrays (click on JSON). Basically you have to search by the node.type key. Other than that I think I like rework, it seems fast and the learning curve is practically non existing :)

silverwind commented 9 years ago

Unwrapping a single document rule looks to be pretty simple:

var css = require("css");
var ast = css.parse(String(fs.readFileSync("github-dark.css")));
ast.stylesheet.rules = ast.stylesheet.rules[0].rules;
console.log(css.stringify(ast));
silverwind commented 9 years ago

Of course, the hard part will be parsing the domain and regexp rules out of that string :)

simov commented 9 years ago
// https://developer.mozilla.org/en-US/docs/Web/CSS/@document

var str = '@-moz-document '+
  'url(\'https://apis.google.com\'),'+
  'url-prefix(https://apis.google.com),'+
  'regexp("^https?://((gist|guides|help|raw|status|developer)\.)?github\.com((?!generated_pages\/preview).)*$"),'+
  'domain("render.githubusercontent.com"),'+
  'domain("raw.githubusercontent.com") {'+

  '@-moz-document url-prefix(https://plus.googleapis.com),'+
  'regexp("https://apis\.google\.com/.*/widget/render/comments\?.*\&first_party_property=YOUTUBE.*"),'+
  'regexp("https://plus\.google\.com/.*/widget/render/comments\?.*"),'+
  'regexp("https://plus\.google\.com/.*/notifications/frame\?.*") {'

// find all matches of re in str
function all (re, str) {
  var result = []
  while (match = re.exec(str)) {
    result.push(match[1])
  }
  return result
}

// get all document rules
var documents = all(/(@-moz-document[^{]*\{)/g, str)
console.log(documents)

// extract all functions from the document rules
var funcs = {url:[], 'url-prefix':[], regexp:[], domain:[]}

documents.forEach(function (rule) {
  Object.keys(funcs).forEach(function (name) {
    if (name != 'regexp') {
      var r = /\((?:'|")?([^'")]+)(?:'|")?\)/
    } else {
      // quotes are required for regexp function
      var r = /\((?:'|")([^'"]+)(?:'|")\)/
    }
    var result = all(new RegExp(name + r.source, 'g'), rule)
    funcs[name] = funcs[name].concat(result)
  })
})

console.log(funcs)
{ url: [ 'https://apis.google.com' ],
  'url-prefix': [ 'https://apis.google.com', 'https://plus.googleapis.com' ],
  regexp: 
   [ '^https?://((gist|guides|help|raw|status|developer).)?github.com((?!generated_pages/preview).)*$',
     'https://apis.google.com/.*/widget/render/comments?.*&first_party_property=YOUTUBE.*',
     'https://plus.google.com/.*/widget/render/comments?.*',
     'https://plus.google.com/.*/notifications/frame?.*' ],
  domain: [ 'render.githubusercontent.com', 'raw.githubusercontent.com' ] }

Basically I'm using the [^characters] any character except in all places. In your case you probably won't need the get all document rules regex. Eventually I'm going to use something similar in my extension.