├── docs ├── demo.mp4 ├── logo.png ├── chrome-logo.png ├── favicons │ ├── favicon.ico │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ms-icon-70x70.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── apple-icon-precomposed.png │ ├── browserconfig.xml │ └── manifest.json ├── loading.svg └── index.html ├── src ├── styles │ ├── _colors.scss │ └── inject.scss ├── icons │ ├── icon16.png │ ├── icon19.png │ ├── icon38.png │ ├── icon48.png │ └── icon128.png ├── _locales │ └── en │ │ └── messages.json ├── content_scripts │ ├── _constants.js │ ├── _templates.js │ └── inject.js ├── service-worker.js ├── manifest.json ├── popup.html └── utils │ └── google-analytics.js ├── .gitignore ├── package.json ├── LICENSE ├── Gruntfile.js ├── update-version.js └── README.md /docs/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/demo.mp4 -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/logo.png -------------------------------------------------------------------------------- /src/styles/_colors.scss: -------------------------------------------------------------------------------- 1 | $color-white: #fff; 2 | $color-black: #000; 3 | $color-gray: #666; -------------------------------------------------------------------------------- /docs/chrome-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/chrome-logo.png -------------------------------------------------------------------------------- /src/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/src/icons/icon16.png -------------------------------------------------------------------------------- /src/icons/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/src/icons/icon19.png -------------------------------------------------------------------------------- /src/icons/icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/src/icons/icon38.png -------------------------------------------------------------------------------- /src/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/src/icons/icon48.png -------------------------------------------------------------------------------- /src/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/src/icons/icon128.png -------------------------------------------------------------------------------- /docs/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/favicon.ico -------------------------------------------------------------------------------- /docs/favicons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon.png -------------------------------------------------------------------------------- /docs/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /docs/favicons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/ms-icon-70x70.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-57x57.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-60x60.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-72x72.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-76x76.png -------------------------------------------------------------------------------- /docs/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /docs/favicons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/ms-icon-150x150.png -------------------------------------------------------------------------------- /docs/favicons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/ms-icon-310x310.png -------------------------------------------------------------------------------- /docs/favicons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/android-icon-36x36.png -------------------------------------------------------------------------------- /docs/favicons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/android-icon-48x48.png -------------------------------------------------------------------------------- /docs/favicons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/android-icon-72x72.png -------------------------------------------------------------------------------- /docs/favicons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/android-icon-96x96.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-114x114.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-120x120.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-144x144.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-152x152.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-180x180.png -------------------------------------------------------------------------------- /docs/favicons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/android-icon-144x144.png -------------------------------------------------------------------------------- /docs/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /docs/favicons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshBardhan/memefy-this/HEAD/docs/favicons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /docs/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Memefy This", 4 | "description": "The name of the application" 5 | }, 6 | "appDescription": { 7 | "message": "The Ultimate Meme Machine where you can makes memes instantly on any online picture.", 8 | "description": "The description of the application" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE specific templates 2 | .idea/ 3 | *.iml 4 | .vs/ 5 | 6 | ## macOS template 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # SublimeText project files 16 | *.sublime-* 17 | 18 | ## vim related 19 | *.swp 20 | *.swo 21 | 22 | ## File dependencies 23 | node_modules/ 24 | *.log 25 | 26 | ## Deployment 27 | dist/ 28 | *.zip -------------------------------------------------------------------------------- /src/content_scripts/_constants.js: -------------------------------------------------------------------------------- 1 | const memeTextSize = { 2 | X_SMALL : 'xsmall', 3 | SMALL : 'small', 4 | MEDIUM : 'medium', 5 | LARGE : 'large', 6 | X_LARGE : 'xlarge' 7 | }; 8 | 9 | const memeTextPosition = { 10 | LEFT: 'left', 11 | CENTER: 'center', 12 | RIGHT: 'right' 13 | }; 14 | 15 | const minImageWidth = 320; 16 | const minImageHeight = 240; 17 | const warningPopupExpiry = 10000; 18 | const defaultPosition = memeTextPosition.CENTER; 19 | const defaultFontSize = memeTextSize.MEDIUM; 20 | const defaultTopText = 'Top Text'; 21 | const defaultBottomText = 'Bottom Text'; -------------------------------------------------------------------------------- /docs/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | import Analytics from './utils/google-analytics.js'; 2 | 3 | // Creates a context menu option that allows your generate meme from the selected image 4 | chrome.runtime.onInstalled.addListener(() => { 5 | chrome.contextMenus.create({ 6 | id: "myContextMenu", 7 | title: "Memefy This Image", 8 | contexts: ["image"] 9 | }); 10 | }); 11 | 12 | // Event listener once the context menu option is selected 13 | chrome.contextMenus.onClicked.addListener((_info, tab) => { 14 | chrome.tabs.sendMessage(tab.id, {msg: "make_meme"}); 15 | }); 16 | 17 | // Event listener that is sent from 'content_scripts' to download the generated meme or track GA event 18 | chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { 19 | if (request?.msg) { 20 | if (request.msg === 'download_meme') { 21 | chrome.tabs.captureVisibleTab(null, {format: 'png', quality: 100}, dataURL => sendResponse({imgSrc: dataURL})); 22 | return true; 23 | } 24 | 25 | if (request.msg === 'track_GA_event') { 26 | Analytics.fireEvent(request.eventName, request.eventParams); 27 | } 28 | } 29 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memefy-this", 3 | "version": "0.1.8", 4 | "description": "Make Memes Online Instantly", 5 | "directories": { 6 | "doc": "docs" 7 | }, 8 | "scripts": { 9 | "postversion": "node update-version.js && git add -u && git commit --amend --no-edit && git push && git push --tags" 10 | }, 11 | "devDependencies": { 12 | "grunt": "^1.3.0", 13 | "grunt-contrib-clean": "^2.0.1", 14 | "grunt-contrib-copy": "^1.0.0", 15 | "grunt-contrib-uglify": "^5.2.2", 16 | "grunt-contrib-watch": "^1.1.0", 17 | "grunt-sass": "^3.1.0", 18 | "grunt-zip": "^0.17.1", 19 | "load-grunt-config": "^4.0.1", 20 | "sass": "^1.87.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/AshBardhan/memefy-this.git" 25 | }, 26 | "keywords": [ 27 | "memes", 28 | "funny", 29 | "trolls", 30 | "jokes" 31 | ], 32 | "author": "AshBardhan", 33 | "license": "ISC", 34 | "bugs": { 35 | "url": "https://github.com/AshBardhan/memefy-this/issues" 36 | }, 37 | "homepage": "https://ashbardhan.github.io/memefy-this" 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ashish Bardhan 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 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Memefy This", 3 | "version": "0.1.8", 4 | "manifest_version": 3, 5 | "description": "Make Memes Online Instantly", 6 | "homepage_url": "https://www.producthunt.com/posts/memefy-this", 7 | "icons": { 8 | "16": "icons/icon16.png", 9 | "48": "icons/icon48.png", 10 | "128": "icons/icon128.png" 11 | }, 12 | "default_locale": "en", 13 | "content_security_policy": { 14 | "extension_pages": "script-src 'self'; object-src 'self'" 15 | }, 16 | "background": { 17 | "service_worker": "service-worker.js", 18 | "type": "module" 19 | }, 20 | "content_scripts": [ 21 | { 22 | "matches": [ 23 | "http://*/*", 24 | "https://*/*", 25 | "file:///*/*" 26 | ], 27 | "css": [ 28 | "content_scripts/inject.css" 29 | ], 30 | "js": [ 31 | "content_scripts/_constants.js", 32 | "content_scripts/_templates.js", 33 | "content_scripts/inject.js" 34 | ] 35 | } 36 | ], 37 | "permissions": [ 38 | "storage", 39 | "contextMenus" 40 | ], 41 | "host_permissions": [ 42 | "" 43 | ], 44 | "action": { 45 | "default_icon": { 46 | "19": "icons/icon19.png", 47 | "38": "icons/icon38.png" 48 | }, 49 | "default_title": "Memefy This", 50 | "default_popup": "popup.html" 51 | } 52 | } -------------------------------------------------------------------------------- /docs/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | const sass = require('sass'); 3 | require('load-grunt-tasks')(grunt); 4 | 5 | grunt.initConfig({ 6 | sass: { 7 | options: { 8 | implementation: sass, 9 | outputStyle: 'expanded', 10 | indentType: 'tab', 11 | sourceMap: false 12 | }, 13 | build: { 14 | files: { 15 | 'dist/content_scripts/inject.css': 'src/styles/inject.scss' 16 | } 17 | } 18 | }, 19 | clean: { 20 | 'build' : ['dist/', 'memefy-this.zip'], 21 | 'dev' : ['dist/'] 22 | }, 23 | copy: { 24 | 'files': { 25 | expand: true, 26 | cwd: 'src', 27 | src: ['**/**', '!styles/**'], 28 | dest: 'dist/' 29 | } 30 | }, 31 | zip: { 32 | 'using-cwd': { 33 | cwd: 'dist/', 34 | src: ['dist/**'], 35 | dest: 'memefy-this.zip' 36 | } 37 | }, 38 | uglify: { 39 | 'js': { 40 | expand: true, 41 | cwd: 'dist', 42 | src: ['**/*.js'], 43 | dest: 'dist/' 44 | } 45 | }, 46 | watch: { 47 | 'src': { 48 | files: ['src/**'], 49 | tasks: ['copy'] 50 | }, 51 | 'sass': { 52 | files: ['src/styles/**'], 53 | tasks: ['sass'] 54 | } 55 | } 56 | }); 57 | 58 | grunt.loadNpmTasks('grunt-contrib-clean'); 59 | grunt.loadNpmTasks('grunt-contrib-copy'); 60 | grunt.loadNpmTasks('grunt-zip'); 61 | grunt.loadNpmTasks('grunt-contrib-uglify'); 62 | grunt.loadNpmTasks('grunt-contrib-watch'); 63 | 64 | grunt.registerTask('build', ['clean:build', 'sass', 'copy', 'uglify', 'zip']); 65 | grunt.registerTask('dev', ['clean:dev', 'sass', 'copy', 'watch']); 66 | }; -------------------------------------------------------------------------------- /src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Memefy This 6 | 60 | 61 | 62 |

Memefy This

63 |
64 |

Version: 0.1.8

65 |

Instructions

66 |
    67 |
  1. Right-click select 'Memefy This Image' option on any image present on the website
  2. 68 |
  3. Add some text at the top and bottom
  4. 69 |
  5. Change the size and position of your texts
  6. 70 |
  7. Download your generated meme
  8. 71 |
72 |
73 | 74 | -------------------------------------------------------------------------------- /update-version.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | // Read the version from package.json 5 | const packageJson = require("./package.json"); 6 | const newVersion = packageJson.version; 7 | 8 | // Define the paths for files to update 9 | const filesToUpdate = [ 10 | path.join(__dirname, "src/manifest.json"), 11 | path.join(__dirname, "README.md"), 12 | path.join(__dirname, "src/popup.html"), // Add any other HTML files here 13 | ]; 14 | 15 | // Function to update the version in JSON files like manifest.json 16 | function updateJsonVersion(filePath, version) { 17 | const file = JSON.parse(fs.readFileSync(filePath, "utf8")); 18 | file.version = version; 19 | fs.writeFileSync(filePath, JSON.stringify(file, null, 2)); 20 | console.log(`Updated version in ${filePath} to ${version}`); 21 | } 22 | 23 | // Function to update markdown files like README.md 24 | function updateMarkdownVersion(filePath, version) { 25 | let fileContent = fs.readFileSync(filePath, "utf8"); 26 | // This regex will look for 'Current version: x.x.x' or 'v.x.x.x' format 27 | const versionRegex = /\*\*Current version:\*\*\s*\d+\.\d+\.\d+/; 28 | const newVersionLine = `**Current version:** ${newVersion}`; 29 | fileContent = fileContent.replace(versionRegex, newVersionLine); 30 | 31 | fs.writeFileSync(filePath, fileContent); 32 | console.log(`Updated version in ${filePath} to ${version}`); 33 | } 34 | 35 | // Function to update version in HTML files 36 | function updateHtmlVersion(filePath, version) { 37 | let fileContent = fs.readFileSync(filePath, "utf8"); 38 | // Replace the version number in the

tag with the version in the HTML file 39 | const versionRegex = /(Version:\s*)(\d+\.\d+\.\d+)/; 40 | fileContent = fileContent.replace(versionRegex, `$1${version}`); 41 | fs.writeFileSync(filePath, fileContent); 42 | console.log(`Updated version in ${filePath} to ${version}`); 43 | } 44 | 45 | // Loop through all the files and update their version 46 | filesToUpdate.forEach((filePath) => { 47 | if (filePath.endsWith(".json")) { 48 | updateJsonVersion(filePath, newVersion); 49 | } else if (filePath.endsWith(".md")) { 50 | updateMarkdownVersion(filePath, newVersion); 51 | } else if (filePath.endsWith(".html")) { 52 | updateHtmlVersion(filePath, newVersion); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Memefy This 2 | 3 | ![app-logo](/docs/logo.png) 4 | 5 | The ultimate meme machine in form of a chrome extension for making instant memes online. 6 | 7 | **Current version:** 0.1.8 8 | 9 | [Download](https://chrome.google.com/webstore/detail/memefy-this/iohemjpgjkgkfgfpiglpfpcclogkelcf) and [View Demo](https://ashbardhan.github.io/memefy-this/) 10 | 11 | If you find this application much useful, show your support by all means 12 | 13 | - Share this app on all social platforms as much as possible. 14 | - Follow [@MemefyThis](https://twitter.com/MemefyThis) on Twitter for more updates and share your memes created from my app. 15 | - Send feedbacks/suggestions with :heart: or :skull: by tweeting to [@CreativeBakchod](https://twitter.com/CreativeBakchod) a.k.a **The Savior Meme-Maker**. 16 | 17 | ## Contributing 18 | 19 | ### Setup 20 | 21 | - [Fork this repo](https://help.github.com/articles/fork-a-repo) and clone it on your system. 22 | - Make sure that you're using node version **v18.7.0** for this application. Use [nvm](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating) for switching to this node versions. 23 | - Install all the required dependencies by running `npm install`. 24 | - Create a new branch out off `master` for your fix/feature by running `git checkout -b new-feature`. 25 | - Build this project by running the following commands 26 | - `grunt dev` - This creates `dist` folder containing unminified files for the chrome extension and a watcher task. 27 | - `grunt build` - This creates `dist` folder containing minified files for the chrome extension and its compressed `.zip` file (only for admin purpose). 28 | - Install in your chrome by loading the generated `dist` folder as an [unpacked extension](http://techapple.net/2015/09/how-to-install-load-unpacked-extension-in-google-chrome-browser-os-chromebooks/). 29 | 30 | ### Things to remember 31 | 32 | - Do not fix multiple issues in a single commit. Keep them one thing per commit so that they can be picked easily in case only few commits require to be merged. 33 | - Before submitting a patch, rebase your branch on upstream `master` to make life easier for the merger. 34 | - Make sure to disable the original downloaded `Memefy This` extension while testing the local unpacked version. 35 | 36 | ### License 37 | 38 | MIT Licensed 39 | 40 | Featured on [Product Hunt](https://www.producthunt.com/posts/memefy-this) and [Hacker News](https://news.ycombinator.com/item?id=15618018) 41 | 42 | Copyright (c) 2024 Ashish Bardhan, [ashbardhan.github.io](https://ashbardhan.github.io) 43 | -------------------------------------------------------------------------------- /src/utils/google-analytics.js: -------------------------------------------------------------------------------- 1 | const GA_ENDPOINT = 'https://www.google-analytics.com/mp/collect'; 2 | const GA_DEBUG_ENDPOINT = 'https://www.google-analytics.com/debug/mp/collect'; 3 | 4 | // Get via https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports 5 | const MEASUREMENT_ID = 'G-YXDE2Z1MDJ'; 6 | const API_SECRET = 'awZ4MEuoSWCYtjw2ZM-2PQ'; 7 | const DEFAULT_ENGAGEMENT_TIME_MSEC = 100; 8 | 9 | // Duration of inactivity after which a new session is created 10 | const SESSION_EXPIRATION_IN_MIN = 30; 11 | 12 | export class Analytics { 13 | constructor(debug = false) { 14 | this.debug = debug; 15 | } 16 | 17 | // Returns the client id, or creates a new one if one doesn't exist. 18 | // Stores client id in local storage to keep the same client id as long as 19 | // the extension is installed. 20 | async getOrCreateClientId() { 21 | let { clientId } = await chrome.storage.local.get('clientId'); 22 | if (!clientId) { 23 | // Generate a unique client ID, the actual value is not relevant 24 | clientId = self.crypto.randomUUID(); 25 | await chrome.storage.local.set({ clientId }); 26 | } 27 | return clientId; 28 | } 29 | 30 | // Returns the current session id, or creates a new one if one doesn't exist or 31 | // the previous one has expired. 32 | async getOrCreateSessionId() { 33 | // Use storage.session because it is only in memory 34 | let { sessionData } = await chrome.storage.session.get('sessionData'); 35 | const currentTimeInMs = Date.now(); 36 | // Check if session exists and is still valid 37 | if (sessionData && sessionData.timestamp) { 38 | // Calculate how long ago the session was last updated 39 | const durationInMin = (currentTimeInMs - sessionData.timestamp) / 60000; 40 | // Check if last update lays past the session expiration threshold 41 | if (durationInMin > SESSION_EXPIRATION_IN_MIN) { 42 | // Clear old session id to start a new session 43 | sessionData = null; 44 | } else { 45 | // Update timestamp to keep session alive 46 | sessionData.timestamp = currentTimeInMs; 47 | await chrome.storage.session.set({ sessionData }); 48 | } 49 | } 50 | if (!sessionData) { 51 | // Create and store a new session 52 | sessionData = { 53 | session_id: currentTimeInMs.toString(), 54 | timestamp: currentTimeInMs.toString() 55 | }; 56 | await chrome.storage.session.set({ sessionData }); 57 | } 58 | return sessionData.session_id; 59 | } 60 | 61 | // Fires an event with optional params. Event names must only include letters and underscores. 62 | async fireEvent(name, params = {}) { 63 | // Configure session id and engagement time if not present, for more details see: 64 | // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#recommended_parameters_for_reports 65 | if (!params.session_id) { 66 | params.session_id = await this.getOrCreateSessionId(); 67 | } 68 | if (!params.engagement_time_msec) { 69 | params.engagement_time_msec = DEFAULT_ENGAGEMENT_TIME_MSEC; 70 | } 71 | 72 | try { 73 | const response = await fetch( 74 | `${this.debug ? GA_DEBUG_ENDPOINT : GA_ENDPOINT}?measurement_id=${MEASUREMENT_ID}&api_secret=${API_SECRET}`, 75 | { 76 | method: 'POST', 77 | body: JSON.stringify({ 78 | client_id: await this.getOrCreateClientId(), 79 | events: [ 80 | { 81 | name, 82 | params 83 | } 84 | ] 85 | }) 86 | } 87 | ); 88 | if (!this.debug) { 89 | return; 90 | } 91 | console.log(await response.text()); 92 | } catch (e) { 93 | console.error('Google Analytics request failed with an exception', e); 94 | } 95 | } 96 | 97 | // Fire a page view event. 98 | async firePageViewEvent(pageTitle, pageLocation, additionalParams = {}) { 99 | return this.fireEvent('page_view', { 100 | page_title: pageTitle, 101 | page_location: pageLocation, 102 | ...additionalParams 103 | }); 104 | } 105 | 106 | // Fire an error event. 107 | async fireErrorEvent(error, additionalParams = {}) { 108 | // Note: 'error' is a reserved event name and cannot be used 109 | // see https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#reserved_names 110 | return this.fireEvent('extension_error', { 111 | ...error, 112 | ...additionalParams 113 | }); 114 | } 115 | } 116 | 117 | export default new Analytics(); -------------------------------------------------------------------------------- /src/styles/inject.scss: -------------------------------------------------------------------------------- 1 | @use 'colors' as *; 2 | 3 | /** 4 | * Injected CSS for Memefy-This 5 | **/ 6 | 7 | ._memefy_body { 8 | position: relative; 9 | overflow-y: hidden; 10 | height: 100vh; 11 | } 12 | 13 | ._memefy_warning-box { 14 | z-index: 1000; 15 | position: fixed; 16 | top: 20px; 17 | left: 50%; 18 | transform: translateX(-50%); 19 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 20 | font-size: 14px; 21 | background: #fee98e; 22 | color: #b89a50; 23 | padding: 10px; 24 | border: 2px solid #eecf49; 25 | border-radius: 4px; 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | opacity: 0; 30 | visibility: hidden; 31 | } 32 | 33 | ._memefy_close-warning-box { 34 | display: inline-flex; 35 | align-items: center; 36 | justify-content: center; 37 | height: 18px; 38 | width: 18px; 39 | margin-left: 10px; 40 | cursor: pointer; 41 | border-radius: 50%; 42 | color: #fee98e; 43 | background: #b89a50; 44 | 45 | &:hover { 46 | color: #f2e6ca; 47 | background: #ccbc75; 48 | } 49 | } 50 | 51 | ._memefy_meme-box-overlay { 52 | position: fixed; 53 | top: 0; 54 | left: 0; 55 | right: 0; 56 | bottom: 0; 57 | overflow: hidden; 58 | z-index: 1000; 59 | background: rgba($color-black, 0.8); 60 | box-sizing: border-box; 61 | } 62 | 63 | ._memefy_meme-box { 64 | position: fixed; 65 | border-radius: 6px; 66 | box-shadow: 0 0 0 2px $color-black, 0 0 0 8px $color-white, 0 0 0 10px $color-black; 67 | z-index: 1001; 68 | left: 50%; 69 | top: 50%; 70 | transform: translate(-50%, -50%); 71 | background-size: 100%; 72 | background-color: $color-white; 73 | box-sizing: border-box; 74 | 75 | *, *:before, *:after { 76 | box-sizing: border-box; 77 | } 78 | 79 | > *:not([class^="_memefy_"]):not([id^="_memefy_"]) { 80 | display: none; 81 | } 82 | } 83 | 84 | ._memefy_meme-text { 85 | position: absolute; 86 | left: 0; 87 | right: 0; 88 | width: 100%; 89 | resize: none !important; 90 | overflow: hidden !important; 91 | padding: 0 5px; 92 | font-family: Impact !important; 93 | line-height: 1.2 !important; 94 | text-transform: uppercase !important; 95 | color: $color-white !important; 96 | background: transparent; 97 | outline: 0 !important; 98 | border: none !important; 99 | letter-spacing: 0.025em !important; 100 | text-shadow: 2px 2px 0 $color-black, -2px -2px 0 $color-black, 2px -2px 0 $color-black, -2px 2px 0 $color-black, 0 2px 0 $color-black, 2px 0 0 $color-black, 0 -2px 0 $color-black, -2px 0 0 $color-black, 2px 2px 5px $color-black !important; 101 | 102 | &.selected { 103 | background: rgba($color-black, 0.35) !important; 104 | } 105 | 106 | &[data-type=bottom] { 107 | bottom: 5px; 108 | } 109 | 110 | &[data-type=top] { 111 | top: 5px; 112 | } 113 | 114 | &[data-pos=left] { 115 | text-align: left !important; 116 | } 117 | 118 | &[data-pos=right] { 119 | text-align: right !important; 120 | } 121 | 122 | &[data-pos=center] { 123 | text-align: center !important; 124 | } 125 | 126 | &[data-size=xsmall] { 127 | font-size: 20px !important; 128 | } 129 | 130 | &[data-size=small] { 131 | font-size: 30px !important; 132 | } 133 | 134 | &[data-size=medium] { 135 | font-size: 40px !important; 136 | } 137 | 138 | &[data-size=large] { 139 | font-size: 50px !important; 140 | } 141 | 142 | &[data-size=xlarge] { 143 | font-size: 60px !important; 144 | } 145 | } 146 | 147 | #_memefy_meme-text-options { 148 | position: absolute; 149 | width: 250px; 150 | height: 30px; 151 | margin: 10px 0; 152 | left: 50%; 153 | transform: translateX(-50%); 154 | display: flex; 155 | align-items: center; 156 | justify-content: space-evenly; 157 | opacity: 0; 158 | visibility: hidden; 159 | background: $color-gray; 160 | border-radius: 4px; 161 | 162 | &::before { 163 | content: ''; 164 | position: absolute; 165 | left: 50%; 166 | transform: translateX(-50%); 167 | border-left: 10px solid transparent; 168 | border-right: 10px solid transparent; 169 | } 170 | 171 | &[type='top']::before { 172 | top: -9px; 173 | border-bottom: 10px solid $color-gray; 174 | } 175 | 176 | &[type='bottom']::before { 177 | bottom: -9px; 178 | border-top: 10px solid $color-gray; 179 | } 180 | } 181 | 182 | ._memefy_btn { 183 | display: inline-flex; 184 | cursor: pointer; 185 | 186 | svg { 187 | width: 20px; 188 | height: 20px; 189 | stroke: rgba($color-white, 0.5); 190 | fill: rgba($color-white, 0.5); 191 | stroke-width: 3px; 192 | } 193 | 194 | &[data-size] svg { 195 | stroke-width: 0; 196 | letter-spacing: 1px; 197 | } 198 | 199 | &:hover, &.selected { 200 | svg { 201 | stroke: rgba($color-white, 1); 202 | fill: rgba($color-white, 1); 203 | } 204 | } 205 | } 206 | 207 | #_memefy_meme-box-options { 208 | position: absolute; 209 | right: -35px; 210 | top: 50%; 211 | list-style: none; 212 | margin: 0; 213 | padding: 0; 214 | transform: translateY(-50%); 215 | 216 | li { 217 | display: flex; 218 | justify-content: center; 219 | align-items: center; 220 | padding: 6px; 221 | background: $color-white; 222 | cursor: pointer; 223 | margin: 8px 0; 224 | height: 20px; 225 | width: 20px; 226 | border-radius: 4px; 227 | box-shadow: 0 0 0 2px $color-black; 228 | 229 | svg { 230 | width: 10px !important; 231 | height: 10px !important; 232 | fill: rgba($color-black, 0.6); 233 | } 234 | 235 | &:hover, &:focus { 236 | svg { 237 | fill: rgba($color-black, 1); 238 | } 239 | } 240 | } 241 | } -------------------------------------------------------------------------------- /src/content_scripts/_templates.js: -------------------------------------------------------------------------------- 1 | const memePopupTemplate = ` 2 | 3 | 4 |

5 |
6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | XS 23 | 24 |
25 |
26 | 27 | S 28 | 29 |
30 |
31 | 32 | M 33 | 34 |
35 |
36 | 37 | L 38 | 39 |
40 |
41 | 42 | XL 43 | 44 |
45 |
46 | 69 | `; 70 | 71 | const warningPopupTemplate = ` 72 | Can't Memefy image below 320 x 240 px in size 73 | 74 | 75 | 76 | 77 | 78 | `; -------------------------------------------------------------------------------- /src/content_scripts/inject.js: -------------------------------------------------------------------------------- 1 | let memeTextOptionSelected = false; 2 | let memeTextOptions = { 3 | 'top': { 4 | 'size': defaultFontSize, 5 | 'pos': defaultPosition, 6 | 'defaultText': defaultTopText 7 | }, 8 | 'bottom': { 9 | 'size': defaultFontSize, 10 | 'pos': defaultPosition, 11 | 'defaultText': defaultBottomText 12 | } 13 | }; 14 | 15 | let selectedImage = selectedTextType = selectedText = null; 16 | let memePopup = memePopupOverlay = memeTextOptionsBox = memeTexts = memePosBtns = memeSizeBtns = null; 17 | let memeDownloadBtn = memeRefreshBtn = memeCancelBtn = null; 18 | let warningPopup = warningPopupTimer = null; 19 | 20 | // Sets the position of meme options box based on type of text 21 | function setMemeTextOptionsBoxPosition() { 22 | memeTextOptionsBox.style.top = (selectedTextType === 'bottom' ? selectedText.offsetTop - memeTextOptionsBox.offsetHeight - 20 : selectedText.offsetTop + selectedText.offsetHeight) + 'px'; 23 | }; 24 | 25 | // Adjusts the height of current meme text element 26 | function adjustHeight(el, minHeight) { 27 | // compute the height difference which is caused by border and outline 28 | const outerHeight = parseInt(window.getComputedStyle(el).height, 10); 29 | const diff = outerHeight - el.clientHeight; 30 | 31 | // set the height to 0 in case of it has to be shrinked 32 | el.style.height = 0; 33 | 34 | // set the correct height 35 | // el.scrollHeight is the full height of the content, not just the visible part 36 | el.style.height = Math.max(minHeight, el.scrollHeight + diff) + 'px'; 37 | }; 38 | 39 | // Sets the height of current meme text element 40 | function setMemeTextHeight(memeText) { 41 | // we adjust height to the initial content 42 | memeText.style.height = 0; 43 | memeText.minHeight = memeText.scrollHeight; 44 | adjustHeight(memeText, memeText.minHeight); 45 | }; 46 | 47 | // Shows meme text options box 48 | function showMemeTextOptionsBox() { 49 | memeTextOptionsBox.style.opacity = 1; 50 | memeTextOptionsBox.style.visibility = 'visible'; 51 | }; 52 | 53 | // Hides meme text options box 54 | function hideMemeTextOptionsBox() { 55 | memeTextOptionsBox.style.opacity = 0; 56 | memeTextOptionsBox.style.visibility = 'hidden'; 57 | }; 58 | 59 | // Sends a GA tracking event handled by the 'service-worker' 60 | function trackGAEvent(eventName, eventParams = {}) { 61 | chrome.runtime.sendMessage({ 62 | msg: 'track_GA_event', 63 | eventName, 64 | eventParams 65 | }); 66 | }; 67 | 68 | // Sets/Unsets various text option buttons based on current settings 69 | function setMemeTextOptionsBox(optionType) { 70 | const memeGroupOptions = memeTextOptionsBox.querySelectorAll(`[data-${optionType}]`); 71 | memeGroupOptions.forEach(memeGroupOption => { 72 | memeGroupOption.classList.remove('selected'); 73 | if (memeGroupOption.getAttribute(`data-${optionType}`) === memeTextOptions[selectedTextType][optionType]) { 74 | memeGroupOption.classList.add('selected'); 75 | } 76 | }); 77 | }; 78 | 79 | // Resets the meme to default settings 80 | function resetMemeTextOptions(memeText) { 81 | const textType = memeText.getAttribute('data-type'); 82 | memeText.value = memeTextOptions[textType]['defaultText']; 83 | memeTextOptions[textType]['size'] = defaultFontSize; 84 | memeTextOptions[textType]['pos'] = defaultPosition; 85 | memeText.setAttribute('data-pos', memeTextOptions[textType]['pos']); 86 | memeText.setAttribute('data-size', memeTextOptions[textType]['size']); 87 | }; 88 | 89 | // Adds event listeners on various components of the meme popup 90 | function setMemePopupEvents() { 91 | memeTexts = memePopup.querySelectorAll('.js-memefy_meme-text'); 92 | memeTextOptionsBox = memePopup.querySelector('.js-memefy_meme-text-options'); 93 | memePosBtns = memePopup.querySelectorAll('.js-memefy_pos-btn'); 94 | memeSizeBtns = memePopup.querySelectorAll('.js-memefy_size-btn'); 95 | memeDownloadBtn = memePopup.querySelector('.js-memefy_download-meme'); 96 | memeCancelBtn = memePopup.querySelector('.js-memefy_cancel-meme'); 97 | memeRefreshBtn = memePopup.querySelector('.js-memefy_refresh-meme'); 98 | 99 | memeTexts.forEach(memeText => { 100 | resetMemeTextOptions(memeText); 101 | setMemeTextHeight(memeText); 102 | 103 | memeText.addEventListener('input', function () { 104 | adjustHeight(this, this.minHeight); 105 | setMemeTextOptionsBoxPosition(); 106 | }); 107 | 108 | memeText.addEventListener('focus', function () { 109 | const type = this.getAttribute('data-type'); 110 | this.classList.add('selected'); 111 | selectedTextType = type; 112 | selectedText = memeText; 113 | setMemeTextOptionsBox('size'); 114 | setMemeTextOptionsBox('pos'); 115 | showMemeTextOptionsBox(); 116 | setMemeTextOptionsBoxPosition(); 117 | }); 118 | 119 | memeText.addEventListener('blur', function () { 120 | if (!memeTextOptionSelected) { 121 | this.classList.remove('selected'); 122 | hideMemeTextOptionsBox(); 123 | } 124 | }); 125 | }); 126 | 127 | memePosBtns.forEach(memePosBtn => { 128 | memePosBtn.addEventListener('mousedown', function () { 129 | const position = this.getAttribute('data-pos'); 130 | memeTextOptionSelected = true; 131 | selectedText.setAttribute('data-pos', position); 132 | memeTextOptions[selectedTextType]['pos'] = position; 133 | setMemeTextOptionsBox('pos'); 134 | setTimeout(() => selectedText.focus(), 0); 135 | }); 136 | 137 | memePosBtn.addEventListener('mouseout', () => { 138 | memeTextOptionSelected = false; 139 | }); 140 | }); 141 | 142 | memeSizeBtns.forEach(memeSizeBtn => { 143 | memeSizeBtn.addEventListener('mousedown', function () { 144 | const fontSize = this.getAttribute('data-size'); 145 | memeTextOptionSelected = true; 146 | selectedText.setAttribute('data-size', fontSize); 147 | memeTextOptions[selectedTextType]['size'] = fontSize; 148 | setMemeTextOptionsBox('size'); 149 | setMemeTextHeight(selectedText); 150 | setMemeTextOptionsBoxPosition(); 151 | setTimeout(() => selectedText.focus(), 0); 152 | }); 153 | 154 | memeSizeBtn.addEventListener('mouseout', () => { 155 | memeTextOptionSelected = false; 156 | }); 157 | }); 158 | 159 | memeCancelBtn.addEventListener('click', () => { 160 | memePopupOverlay.parentNode.removeChild(memePopupOverlay); 161 | memePopup.parentNode.removeChild(memePopup); 162 | document.body.classList.remove('_memefy_body'); 163 | trackGAEvent('meme_cancel'); 164 | }); 165 | 166 | function onImgLoad(image) { 167 | let c = document.createElement('canvas'); 168 | const iframeBounds = memePopup.getBoundingClientRect(); 169 | c.width = iframeBounds.width; 170 | c.height = iframeBounds.height; 171 | c.style.display = 'none'; 172 | 173 | const devicePixelRatio = window.devicePixelRatio || 1; 174 | 175 | let ctx = c.getContext('2d'); 176 | ctx.drawImage( 177 | image, 178 | iframeBounds.left * devicePixelRatio, 179 | iframeBounds.top * devicePixelRatio, 180 | iframeBounds.width * devicePixelRatio, 181 | iframeBounds.height * devicePixelRatio, 182 | 0, 183 | 0, 184 | iframeBounds.width, 185 | iframeBounds.height 186 | ); 187 | image.removeEventListener('load', onImgLoad); 188 | 189 | const d = new Date(); 190 | const fileName = [ 191 | 'memefy', 192 | d.getFullYear(), 193 | d.getMonth() + 1, 194 | d.getDate(), 195 | d.getHours(), 196 | d.getMinutes(), 197 | d.getSeconds() 198 | ].join('-') + '.png'; 199 | 200 | let link = document.createElement('a'); 201 | link.href = c.toDataURL(); 202 | link.download = fileName; 203 | document.body.appendChild(link); 204 | link.addEventListener('click', e => e.stopPropagation()); 205 | link.click(); 206 | link.parentNode.removeChild(link); 207 | } 208 | 209 | memeDownloadBtn.addEventListener('click', () => { 210 | setTimeout(() => { 211 | chrome.runtime.sendMessage({msg: 'download_meme'}, response => { 212 | if (response?.imgSrc) { 213 | let image = new Image(); 214 | image.src = response.imgSrc; 215 | image.addEventListener('load', () => onImgLoad(image)); 216 | trackGAEvent('meme_download'); 217 | } 218 | }); 219 | }, 0); 220 | }); 221 | 222 | memeRefreshBtn.addEventListener('click', () => { 223 | memeTexts.forEach(memeText => { 224 | resetMemeTextOptions(memeText); 225 | setMemeTextHeight(memeText); 226 | }); 227 | trackGAEvent('meme_refresh'); 228 | }); 229 | }; 230 | 231 | // Hides warning popup after manual or automatic close 232 | function hideWarningPopup() { 233 | warningPopup.style.opacity = 0; 234 | warningPopup.style.visibility = 'hidden'; 235 | }; 236 | 237 | // Shows warning popup when the selected image is not qualified for generating meme 238 | function showWarningPopup() { 239 | if (!warningPopup) { 240 | warningPopup = document.createElement('div'); 241 | warningPopup.classList.add('js-memefy_warning-box', '_memefy_warning-box'); 242 | warningPopup.innerHTML = warningPopupTemplate; 243 | document.body.appendChild(warningPopup); 244 | 245 | warningPopup.querySelector('.js-memefy_close-warning-box').addEventListener('click', () => { 246 | clearTimeout(warningPopupTimer); 247 | hideWarningPopup(); 248 | }); 249 | } 250 | 251 | warningPopup.style.opacity = 1; 252 | warningPopup.style.visibility = 'visible'; 253 | clearTimeout(warningPopupTimer); 254 | warningPopupTimer = setTimeout(() => { 255 | hideWarningPopup(); 256 | }, warningPopupExpiry); 257 | 258 | trackGAEvent('warning_show'); 259 | }; 260 | 261 | // Creates a popup where the meme can be edited and downloaded 262 | function createMemePopup() { 263 | let selectedImageWidth = selectedImage.width, 264 | selectedImageHeight = selectedImage.height, 265 | windowWidth = window.innerWidth, 266 | windowHeight = window.innerHeight, 267 | memePopupWidth = selectedImageWidth, 268 | memePopupHeight = selectedImageHeight; 269 | 270 | memePopup = document.createElement('div'); 271 | memePopup.classList.add('js-memefy_meme-box', '_memefy_meme-box'); 272 | 273 | if (memePopupWidth >= windowWidth) { 274 | memePopupWidth = windowWidth - 30; 275 | memePopupHeight = selectedImageHeight / selectedImageWidth * memePopupWidth; 276 | } 277 | 278 | if (memePopupHeight >= windowHeight) { 279 | memePopupHeight = windowHeight - 10; 280 | memePopupWidth = selectedImageWidth / selectedImageHeight * memePopupHeight; 281 | } 282 | 283 | memePopup.style.width = `${memePopupWidth}px`; 284 | memePopup.style.height = `${memePopupHeight}px`; 285 | 286 | memePopup.style.backgroundImage = `url("${selectedImage.src}")`; 287 | memePopup.innerHTML= memePopupTemplate; 288 | document.body.appendChild(memePopup); 289 | 290 | memePopupOverlay = document.createElement('div'); 291 | memePopupOverlay.classList.add('js-memefy_meme-box-overlay', '_memefy_meme-box-overlay'); 292 | document.body.appendChild(memePopupOverlay); 293 | document.body.classList.add('_memefy_body'); 294 | trackGAEvent('meme_show', { 295 | 'width': `${memePopupWidth}px`, 296 | 'height': `${memePopupHeight}px` 297 | }); 298 | setMemePopupEvents(); 299 | }; 300 | 301 | // Event listener that stores the target element (selected image) once you right-click on it 302 | document.addEventListener("mousedown", e => { 303 | if (e.button == 2) { 304 | selectedImage = e.target; 305 | } 306 | }, true); 307 | 308 | // Event listener that is sent from 'service-worker' to create meme 309 | chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { 310 | if (request?.msg === "make_meme") { 311 | trackGAEvent('image_select', { 312 | 'width': `${selectedImage.width}px`, 313 | 'height': `${selectedImage.height}px` 314 | }); 315 | // Check whether the selected image is qualified to create the meme 316 | if (selectedImage.width >= minImageWidth && selectedImage.height >= minImageHeight) { 317 | createMemePopup(); 318 | sendResponse({ele: selectedImage.innerHTML}); 319 | } else { 320 | showWarningPopup(); 321 | } 322 | return true; 323 | } 324 | }); -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Memefy This - Make Memes Online Instantly 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 219 | 220 | 221 |
222 |
223 |
224 |

225 | 226 |

227 |
228 | 249 |
250 | 294 |
295 |
296 | 297 |
298 |
299 |

The Ultimate Meme Machine

300 |

This meme generator is dedicated to those crazy people who can't live without making memes.

301 |

Experience the instant making of memes online like never before.

302 |

Just try it yourself!

303 | 307 |
308 |
309 |
310 | 311 | 312 | 313 | --------------------------------------------------------------------------------