├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── basic.js └── choo.js ├── index.js ├── loader.js ├── package.json ├── screenshot.png └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .nyc_output/ 3 | coverage/ 4 | dist/ 5 | tmp/ 6 | npm-debug.log* 7 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: node_js 3 | node_js: 4 | - 'node' 5 | sudo: false 6 | addons: 7 | apt: 8 | packages: 9 | - xvfb 10 | cache: 11 | directories: 12 | - ~/.npm 13 | install: 14 | - export DISPLAY=':99.0' 15 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 16 | - npm i 17 | script: 18 | - npm test 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # twitter-component Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 1.0.2 - 2017-09-11 6 | * Add nanoassert as [browser dependency](https://github.com/browserify/browserify-handbook#browser-field). 7 | * Add yo-yoify as a [local transform](https://github.com/browserify/browserify-handbook#browserifytransform-field). 8 | 9 | ## 1.0.1 - 2017-08-19 10 | * Fix sibling re-ordering 11 | * Add option to disable placeholder text 12 | 13 | ## 1.0.0 - 2017-08-17 14 | * Engage 🚀 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Bret Comnes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # twitter-component [![stability][0]][1] 2 | [![npm version][2]][3] [![build status][4]][5] 3 | [![downloads][8]][9] [![js-standard-style][10]][11] 4 | 5 | A native DOM component wrapper for embedded tweets. 6 | 7 | ![](screenshot.png) 8 | 9 | ## Usage 10 | 11 | ```js 12 | // Vanilla JS example 13 | var TwitterComponent = require('twitter-component') 14 | 15 | var tweet1 = new TwitterComponent() 16 | var tweet2 = new TwitterComponent() 17 | 18 | document.body.appendChild(tweet1.render('https://twitter.com/uhhyeahbret/status/897603426518876161')) 19 | document.body.appendChild(tweet2.render('https://twitter.com/yoshuawuyts/status/895338700531535878')) 20 | 21 | ``` 22 | 23 | ## Installation 24 | ```sh 25 | $ npm install twitter-component 26 | ``` 27 | ## API 28 | ### `TwitterComponent = require('twitter-component`) 29 | Import `TwitterComponent` component class. 30 | 31 | ### `tweet = new TwitterComponent([opts])` 32 | Create a new instance of the twitter component. `opts` is an options objec that can have the following options: 33 | 34 | ```js 35 | { 36 | placeholder: true // Enables placeholder text while loading tweet cards 37 | } 38 | ``` 39 | 40 | ### `tweet.render(tweetURL)` 41 | Returns a div that, when mounted into the page, will be the target of `twttr.widgets.createTweet`. 42 | Mounting the DOM node returned by `.render` will also load `platform.twitter.com/widgets.js` into the page, and any other side-effects and visitor tracking implications that script brings along with it. 43 | 44 | **Twitter employees:** Please release a module version of `platform.twitter.com/widgets.js` because this took 5 hours of freetime™ of just trying to get caught up with your ever changing API and widget ecosystem. 45 | 46 | Native DOM component model powered by [nanocomponent][nc] and [nanomorph][nm]. 47 | 48 | ## License 49 | [MIT](https://tldrlegal.com/license/mit-license) 50 | 51 | [0]: https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square 52 | [1]: https://nodejs.org/api/documentation.html#documentation_stability_index 53 | [2]: https://img.shields.io/npm/v/twitter-component.svg?style=flat-square 54 | [3]: https://npmjs.org/package/twitter-component 55 | [4]: https://img.shields.io/travis/bcomnes/twitter-component/master.svg?style=flat-square 56 | [5]: https://travis-ci.org/bcomnes/twitter-component 57 | [8]: http://img.shields.io/npm/dm/twitter-component.svg?style=flat-square 58 | [9]: https://npmjs.org/package/twitter-component 59 | [10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square 60 | [11]: https://github.com/feross/standard 61 | [bel]: https://github.com/shama/bel 62 | [yoyoify]: https://github.com/shama/yo-yoify 63 | [md]: https://github.com/patrick-steele-idem/morphdom 64 | [210]: https://github.com/patrick-steele-idem/morphdom/pull/81 65 | [nm]: https://github.com/yoshuawuyts/nanomorph 66 | [ce]: https://github.com/yoshuawuyts/cache-element 67 | [class]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes 68 | [isSameNode]: https://github.com/choojs/nanomorph#caching-dom-elements 69 | [onload]: https://github.com/shama/on-load 70 | [choo]: https://github.com/choojs/choo 71 | [nca]: https://github.com/choojs/nanocomponent-adapters 72 | [nc]: https://github.com/choojs/nanocomponent 73 | -------------------------------------------------------------------------------- /example/basic.js: -------------------------------------------------------------------------------- 1 | var Tweet = require('../') 2 | 3 | var tweet1 = new Tweet() 4 | var tweet2 = new Tweet() 5 | 6 | document.body.appendChild(tweet1.render('https://twitter.com/uhhyeahbret/status/897603426518876161')) 7 | document.body.appendChild(tweet2.render('https://twitter.com/yoshuawuyts/status/895338700531535878')) 8 | -------------------------------------------------------------------------------- /example/choo.js: -------------------------------------------------------------------------------- 1 | var html = require('choo/html') 2 | var choo = require('choo') 3 | var Nanomap = require('nanomap') 4 | var twitterFeed = require('noauth-twitterfeed') 5 | var Tweet = require('../') 6 | 7 | var app = choo() 8 | app.use(tweetStore) 9 | app.route('/', mainView) 10 | if (typeof window !== 'undefined') app.mount('body') 11 | 12 | var tweetMap = new Nanomap(Tweet) 13 | 14 | function shape (tweet, i, array) { 15 | return { 16 | id: tweet.url, 17 | arguments: tweet.url 18 | } 19 | } 20 | 21 | function mainView (state, emit) { 22 | function onInput (ev) { 23 | emit('input', ev.target.value) 24 | } 25 | 26 | function onUpdate (ev) { 27 | console.log('click') 28 | emit('update') 29 | } 30 | 31 | return html` 32 | 33 |
34 |

Embed some tweets in Choo

35 | 36 | 37 | ${state.err ? state.err.message : state.tweets.map(shape).map(tweetMap)} 38 |
39 | ` 40 | } 41 | 42 | function tweetStore (state, emitter) { 43 | state.tweets = [] 44 | state.err = null 45 | state.input = 'ninabreznik' 46 | emitter.on('DOMContentLoaded', function () { 47 | emitter.on('input', function (val) { 48 | state.input = val 49 | }) 50 | emitter.on('update', function () { 51 | twitterFeed({ 52 | username: state.input 53 | }, function (err, tweets) { 54 | if (err) { 55 | state.tweets = [] 56 | state.err = err 57 | } else { 58 | state.tweets = tweets 59 | } 60 | emitter.emit('render') 61 | }) 62 | }) 63 | emitter.emit('update') 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Nanocomponent = require('nanocomponent') 2 | var html = require('bel') 3 | var onIdle = require('on-idle') 4 | var url = require('url') 5 | var twitterWidgetsLoader = require('./loader') 6 | 7 | class TwitterComponent extends Nanocomponent { 8 | constructor (opts) { 9 | super() 10 | opts = Object.assign({ 11 | placeholder: true 12 | }, opts) 13 | this.opts = opts 14 | } 15 | 16 | createElement (tweetURL) { 17 | this.tweetURL = tweetURL 18 | if (this.opts.placeholder) return html`
loading tweet: ${tweetURL}
` 19 | return html`
` 20 | } 21 | 22 | update (tweetURL) { 23 | return this.tweetURL !== tweetURL 24 | } 25 | 26 | loadTweet (el) { 27 | var tweetID = url.parse(this.tweetURL).pathname.split('/').pop() 28 | if (!el) return console.warn(`cant render ${tweetID} on unmounted component`) 29 | onIdle(function () { 30 | window.requestAnimationFrame(function () { 31 | twitterWidgetsLoader.load(function (twttr) { 32 | while (el.hasChildNodes()) el.removeChild(el.lastChild) 33 | twttr.widgets.createTweet(tweetID, el) 34 | }) 35 | }) 36 | }) 37 | } 38 | 39 | load (el) { 40 | this.loadTweet(el) 41 | } 42 | 43 | afterupdate (el) { 44 | this.loadTweet(el) 45 | } 46 | 47 | afterreorder (el) { 48 | this.loadTweet(el) 49 | } 50 | } 51 | 52 | module.exports = TwitterComponent 53 | -------------------------------------------------------------------------------- /loader.js: -------------------------------------------------------------------------------- 1 | // Fixed a Broken UMD from twitter-widgets 2 | // aka https://github.com/Prinzhorn/twitter-widgets 3 | var TwitterWidgetsLoader = { 4 | src: '//platform.twitter.com/widgets.js', 5 | loading: false, 6 | listeners: [], 7 | interval: 50, 8 | 9 | load: function (callback) { 10 | var _this = this 11 | 12 | this.listeners.push(callback) 13 | 14 | if (window.twttr && window.twttr.widgets) { 15 | setTimeout(function () { 16 | _this.done() 17 | }) 18 | return 19 | } 20 | 21 | if (this.loading) { 22 | return 23 | } 24 | 25 | this.loading = true 26 | 27 | var script = document.createElement('script') 28 | script.type = 'text/javascript' 29 | script.src = this.src 30 | document.body.appendChild(script) 31 | 32 | this.poll() 33 | }, 34 | 35 | poll: function () { 36 | if (window.twttr && window.twttr.widgets) { 37 | return this.done() 38 | } 39 | 40 | var _this = this 41 | 42 | setTimeout(function () { 43 | _this.poll() 44 | }, this.interval) 45 | }, 46 | 47 | done: function () { 48 | while (this.listeners.length) { 49 | this.listeners.pop()(window.twttr) 50 | } 51 | } 52 | } 53 | 54 | module.exports = TwitterWidgetsLoader 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitter-component", 3 | "description": "A native DOM component wrapper for embedded tweets", 4 | "version": "1.0.2", 5 | "author": "Bret Comnes", 6 | "bugs": { 7 | "url": "https://github.com/bcomnes/twitter-component/issues" 8 | }, 9 | "browser": { 10 | "assert": "nanoassert" 11 | }, 12 | "browserify": { 13 | "transform": [ 14 | "yo-yoify" 15 | ] 16 | }, 17 | "dependencies": { 18 | "bel": "^5.0.3", 19 | "nanoassert": "^1.1.0", 20 | "nanocomponent": "^6.4.1", 21 | "on-idle": "^3.1.2", 22 | "url": "^0.11.0", 23 | "yo-yoify": "^3.7.3" 24 | }, 25 | "devDependencies": { 26 | "@tap-format/spec": "^0.2.0", 27 | "bankai": "^9.0.0-1", 28 | "browserify": "^14.4.0", 29 | "budo": "^10.0.4", 30 | "choo": "^6.0.1", 31 | "dependency-check": "^2.9.1", 32 | "global": "^4.3.2", 33 | "nanomap": "^1.0.0", 34 | "noauth-twitterfeed": "^2.0.1", 35 | "npm-run-all": "^4.0.2", 36 | "standard": "^10.0.3", 37 | "tape": "^4.7.0", 38 | "tape-run": "^3.0.0", 39 | "twitter-widgets": "^1.0.0" 40 | }, 41 | "homepage": "https://github.com/bcomnes/twitter-component#readme", 42 | "keywords": [ 43 | "bel", 44 | "cache-component", 45 | "choo", 46 | "element", 47 | "embed", 48 | "nanocomponent", 49 | "nanomorph", 50 | "tweet", 51 | "twitter" 52 | ], 53 | "license": "MIT", 54 | "main": "index.js", 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/bcomnes/twitter-component.git" 58 | }, 59 | "scripts": { 60 | "start": "budo example/basic.js --live --open", 61 | "start:choo": "budo example/choo.js --live --open", 62 | "test": "run-s test:*", 63 | "test:browser": "browserify test.js | tape-run | tap-format-spec", 64 | "test:deps": "dependency-check .", 65 | "test:lint": "standard", 66 | "build": "bankai build example/choo.js dist" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcomnes/twitter-component/36a3941036b5e0e9c0ad3ee824b78e52ab3cc2e1/screenshot.png -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var window = require('global/window') 3 | var TwitterComponent = require('./') 4 | 5 | function makeID () { 6 | return 'testid-' + Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) 7 | } 8 | 9 | function createTestElement () { 10 | var testRoot = document.createElement('div') 11 | testRoot.id = makeID() 12 | document.body.appendChild(testRoot) 13 | return testRoot 14 | } 15 | 16 | function renderAndMount (testEl, tweet) { 17 | var el = tweet.render('https://twitter.com/uhhyeahbret/status/897603426518876161') 18 | testEl.appendChild(el) 19 | } 20 | 21 | test('render a tweet maybe', function (t) { 22 | var testRoot = createTestElement() 23 | var tweet = new TwitterComponent() 24 | 25 | t.doesNotThrow(renderAndMount.bind(null, testRoot, tweet), 'Able to render a tweet') 26 | 27 | window.setTimeout(function () { 28 | t.true(tweet.element.children[0].classList.contains('twitter-tweet'), 'A tweet iframe is added') 29 | t.end() 30 | }, 1000) 31 | }) 32 | --------------------------------------------------------------------------------