"
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 |
--------------------------------------------------------------------------------
/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 | - Right-click select 'Memefy This Image' option on any image present on the website
68 | - Add some text at the top and bottom
69 | - Change the size and position of your texts
70 | - Download your generated meme
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 | 
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 |
10 |
15 |
20 |
21 |
24 |
25 |
26 |
29 |
30 |
31 |
34 |
35 |
36 |
39 |
40 |
41 |
44 |
45 |
46 |
47 | -
48 |
53 |
54 | -
55 |
60 |
61 | -
62 |
67 |
68 |
69 | `;
70 |
71 | const warningPopupTemplate = `
72 | Can't Memefy image below 320 x 240 px in size
73 |
74 |
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 |
--------------------------------------------------------------------------------