Extract renderer to separate cjs/mjs module

Add typescript declarations (verified using `tsd`).
This commit is contained in:
Dario Vladovic 2020-07-13 01:26:41 +02:00
parent 05f84c839a
commit 2c673f6333
No known key found for this signature in database
GPG Key ID: 87F43AAF74B61286
9 changed files with 264 additions and 107 deletions

26
index.d.ts vendored Normal file
View File

@ -0,0 +1,26 @@
interface PublishPreset {
url: string;
title: string;
extra: {
media: string;
text: string;
via: string;
};
}
type Network =
'facebook' |
'linkedin' |
'messenger' |
'odnoklassniki' |
'pinterest' |
'pocket' |
'reddit' |
'telegram' |
'twitter' |
'viber' |
'vkontakte' |
'whatsapp';
export function shareUrl(network: Network, preset?: PublishPreset): string | void;
export function render(container: HTMLElement): HTMLElement;

5
index.test-d.ts Normal file
View File

@ -0,0 +1,5 @@
import { expectError } from 'tsd';
import { shareUrl } from '.';
shareUrl('facebook')
expectError(shareUrl('dummy'))

View File

@ -29,6 +29,7 @@
"import": "./dist/shareon.mjs"
},
"files": [
"index.d.ts",
"dist"
],
"scripts": {
@ -36,6 +37,7 @@
"dev": "rollup -w -c",
"pretest": "run-s build",
"test:lint": "eslint-ci --ext .js,.ts ./src/",
"test:dts": "tsd",
"test:size": "size-limit",
"test": "run-p test:*",
"postversion": "run-s build"
@ -61,6 +63,7 @@
"rollup-plugin-terser": "^6.1.0",
"sass": "^1.26.9",
"size-limit": "^4.5.2",
"tsd": "^0.13.1",
"typescript": "^3.8.3"
},
"size-limit": [

View File

@ -10,7 +10,8 @@ const isDev = process.env.ROLLUP_WATCH || process.env.NODE_ENV === 'development'
const pkg = require('./package.json');
const inputFile = './src/index.ts';
const mainFile = './src/index.ts';
const rendererFile = './src/container.ts';
const outputDir = isDev ? './dev/' : './dist/';
const bannerText = `${pkg.name} v${pkg.version} by Nikita Karamov\n${pkg.homepage}`;
@ -56,30 +57,46 @@ plugins.push(postcss({
* OUTPUTS
*/
const output = [];
/** @type {Array<import('rollup').RollupOptions>} */
const config = [];
if (isDev) {
output.push({
name: pkg.name,
format: 'iife',
file: `${outputDir}${pkg.name}.js`,
config.push({
input: mainFile,
output: {
name: pkg.name,
format: 'iife',
file: `${outputDir}${pkg.name}.js`,
},
plugins,
});
} else {
output.push({
name: pkg.name,
format: 'cjs',
file: `${outputDir}${pkg.name}.cjs`,
config.push({
input: rendererFile,
output: {
name: pkg.name,
format: 'cjs',
file: `${outputDir}${pkg.name}.cjs`,
},
plugins,
});
output.push({
name: pkg.name,
format: 'esm',
file: `${outputDir}${pkg.name}.mjs`,
config.push({
input: rendererFile,
output: {
name: pkg.name,
format: 'esm',
file: `${outputDir}${pkg.name}.mjs`,
},
plugins,
});
output.push({
name: pkg.name,
format: 'iife',
file: `${outputDir}${pkg.name}.min.js`,
plugins: [terser({ output: { comments: false } })],
config.push({
input: mainFile,
output: {
name: pkg.name,
format: 'iife',
file: `${outputDir}${pkg.name}.min.js`,
},
plugins: plugins.concat(terser({ output: { comments: false } })),
});
}
@ -87,8 +104,4 @@ if (isDev) {
* EXPORT
*/
export default {
input: inputFile,
output,
plugins,
};
export default config;

42
src/container.ts Normal file
View File

@ -0,0 +1,42 @@
import { Network, PublishPreset } from './types';
import { networks, shareUrl } from './networks';
function renderChild(child: HTMLElement, preset: PublishPreset) {
const network = (Array.from(child.classList) as Network[])
.reverse()
.find((cls) => networks.includes(cls));
if (!network) return;
const url = shareUrl(network, preset);
if (!url) return;
if (child.tagName.toLowerCase() === 'a') {
child.setAttribute('href', url);
child.setAttribute('rel', 'noopener noreferrer');
child.setAttribute('target', '_blank');
} else {
child.addEventListener('click', () => { window.open(url, '_blank', 'noopener,noreferrer').opener = null; });
}
}
export function render(container: HTMLElement): HTMLElement {
for (let i = 0; i < container.children.length; i += 1) {
const child = container.children[i] as HTMLElement;
const preset: PublishPreset = {
url: child.dataset.url || container.dataset.url || window.location.href,
title: child.dataset.title || container.dataset.title || document.title,
extra: {
media: child.dataset.media || container.dataset.media,
text: child.dataset.text || container.dataset.text,
via: child.dataset.via || container.dataset.via,
},
};
renderChild(child, preset);
}
return container;
}
export { shareUrl };

View File

@ -1,89 +1,11 @@
import './style.scss';
interface PublishPreset {
url: string,
title: string,
extra: {
media: string,
text: string,
via: string,
}
}
type UrlBuilder = (data: PublishPreset) => string;
function hasProp(obj: Record<string, unknown>, prop: unknown): boolean {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function createUrl(baseUrl: string, params: Record<string, unknown>): string {
const url = new URL(baseUrl);
Object.keys(params).forEach((name) => {
const value = String(params[name] ?? '');
if (value) url.searchParams.append(name, value);
});
return url.href;
}
function createMessage({ title, url, extra }: PublishPreset): string {
const sep = '\r\n';
return [
title,
url,
extra.text && `${sep}extra.text`,
].filter(Boolean).join(sep);
}
const NETWORKS: { [name: string]: UrlBuilder } = {
facebook: (d) => createUrl('https://www.facebook.com/sharer/sharer.php', { u: d.url }),
linkedin: (d) => createUrl('https://www.linkedin.com/shareArticle', { mini: true, url: d.url, title: d.title }),
messenger: (d) => createUrl('https://www.facebook.com/dialog/send', { app_id: 3619024578167617, link: d.url, redirect_uri: d.url }),
odnoklassniki: (d) => createUrl('https://connect.ok.ru/offer', { url: d.url, title: d.title, imageUrl: d.extra.media }),
pinterest: (d) => createUrl('https://pinterest.com/pin/create/button', { url: d.url, description: d.title, media: d.extra.media }),
pocket: (d) => createUrl('https://getpocket.com/edit.php', { url: d.url }),
reddit: (d) => createUrl('https://www.reddit.com/submit', { title: d.title, url: d.url }),
telegram: (d) => createUrl('https://telegram.me/share/url', { url: d.url, text: d.extra.text }),
twitter: (d) => createUrl('https://twitter.com/intent/tweet', { url: d.url, text: d.title, via: d.extra.via }),
viber: (d) => createUrl('viber://forward', { text: createMessage(d) }),
vkontakte: (d) => createUrl('https://vk.com/share.php', { url: d.url, title: d.title, image: d.extra.media }),
whatsapp: (d) => createUrl('whatsapp://send', { text: createMessage(d) }),
};
function initShareonChild(child: HTMLElement, preset: PublishPreset) {
child.classList.forEach((cls) => {
if (!hasProp(NETWORKS, cls)) return;
const url = NETWORKS[cls](preset);
if (child.tagName.toLowerCase() === 'a') {
child.setAttribute('href', url);
child.setAttribute('rel', 'noopener noreferrer');
child.setAttribute('target', '_blank');
} else {
child.addEventListener('click', () => { window.open(url, '_blank', 'noopener,noreferrer').opener = null; });
}
});
}
import { render as renderContainer } from './container';
window.addEventListener('DOMContentLoaded', () => {
const shareonContainers = document.getElementsByClassName('shareon');
for (let i = 0; i < shareonContainers.length; i += 1) {
const container = shareonContainers[i] as HTMLElement;
for (let j = 0; j < container.children.length; j += 1) {
const child = container.children[j] as HTMLElement;
const preset: PublishPreset = {
url: child.dataset.url || container.dataset.url || window.location.href,
title: child.dataset.title || container.dataset.title || document.title,
extra: {
media: child.dataset.media || container.dataset.media,
text: child.dataset.text || container.dataset.text,
via: child.dataset.via || container.dataset.via,
},
};
initShareonChild(child, preset);
}
renderContainer(container);
}
});

45
src/networks.ts Normal file
View File

@ -0,0 +1,45 @@
import { Network, PublishPreset } from './types';
type UrlBuilder = (data: PublishPreset) => string;
function createUrl(baseUrl: string, params: Record<string, unknown>): string {
const url = new URL(baseUrl);
Object.keys(params).forEach((name) => {
const value = String(params[name] ?? '');
if (value) url.searchParams.append(name, value);
});
return url.href;
}
function createMessage({ title, url, extra }: PublishPreset): string {
const sep = '\r\n';
return [
title,
url,
extra.text && `${sep}extra.text`,
].filter(Boolean).join(sep);
}
const NETWORKS: { [N in Network]: UrlBuilder } = {
facebook: (d) => createUrl('https://www.facebook.com/sharer/sharer.php', { u: d.url }),
linkedin: (d) => createUrl('https://www.linkedin.com/shareArticle', { mini: true, url: d.url, title: d.title }),
messenger: (d) => createUrl('https://www.facebook.com/dialog/send', { app_id: 3619024578167617, link: d.url, redirect_uri: d.url }),
odnoklassniki: (d) => createUrl('https://connect.ok.ru/offer', { url: d.url, title: d.title, imageUrl: d.extra.media }),
pinterest: (d) => createUrl('https://pinterest.com/pin/create/button', { url: d.url, description: d.title, media: d.extra.media }),
pocket: (d) => createUrl('https://getpocket.com/edit.php', { url: d.url }),
reddit: (d) => createUrl('https://www.reddit.com/submit', { title: d.title, url: d.url }),
telegram: (d) => createUrl('https://telegram.me/share/url', { url: d.url, text: d.extra.text }),
twitter: (d) => createUrl('https://twitter.com/intent/tweet', { url: d.url, text: d.title, via: d.extra.via }),
viber: (d) => createUrl('viber://forward', { text: createMessage(d) }),
vkontakte: (d) => createUrl('https://vk.com/share.php', { url: d.url, title: d.title, image: d.extra.media }),
whatsapp: (d) => createUrl('whatsapp://send', { text: createMessage(d) }),
};
export const networks = Object.keys(NETWORKS) as Network[];
export function shareUrl(network: Network, preset?: PublishPreset): string | void {
if (networks.includes(network)) {
return NETWORKS[network](preset);
}
return undefined;
}

23
src/types.ts Normal file
View File

@ -0,0 +1,23 @@
export type Network =
'facebook' |
'linkedin' |
'messenger' |
'odnoklassniki' |
'pinterest' |
'pocket' |
'reddit' |
'telegram' |
'twitter' |
'viber' |
'vkontakte' |
'whatsapp';
export interface PublishPreset {
url: string,
title: string,
extra: {
media: string,
text: string,
via: string,
}
}

View File

@ -608,6 +608,11 @@ arrify@^1.0.1:
resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
arrify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
asn1.js@^4.0.0:
version "4.10.1"
resolved "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
@ -988,6 +993,11 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
camelcase@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
caniuse-api@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
@ -1031,7 +1041,7 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0:
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
@ -2030,6 +2040,19 @@ eslint-config-airbnb@^18.1.0:
object.assign "^4.1.0"
object.entries "^1.1.2"
eslint-formatter-pretty@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eslint-formatter-pretty/-/eslint-formatter-pretty-4.0.0.tgz#dc15f3bf4fb51b7ba5fbedb77f57ba8841140ce2"
integrity sha512-QgdeZxQwWcN0TcXXNZJiS6BizhAANFhCzkE7Yl9HKB7WjElzwED6+FbbZB2gji8ofgJTGPqKm6VRCNT3OGCeEw==
dependencies:
ansi-escapes "^4.2.1"
chalk "^4.1.0"
eslint-rule-docs "^1.1.5"
log-symbols "^4.0.0"
plur "^4.0.0"
string-width "^4.2.0"
supports-hyperlinks "^2.0.0"
eslint-import-resolver-node@^0.3.3:
version "0.3.4"
resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
@ -2065,6 +2088,11 @@ eslint-plugin-import@^2.21.2:
resolve "^1.17.0"
tsconfig-paths "^3.9.0"
eslint-rule-docs@^1.1.5:
version "1.1.199"
resolved "https://registry.yarnpkg.com/eslint-rule-docs/-/eslint-rule-docs-1.1.199.tgz#f4e0befb6907101399624964ce4726f684415630"
integrity sha512-0jXhQ2JLavUsV/8HVFrBSHL4EM17cl0veZHAVcF1HOEoPdrr09huADK9/L7CbsqP4tMJy9FG23neUEDH8W/Mmg==
eslint-scope@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
@ -3053,6 +3081,11 @@ ipaddr.js@1.9.1:
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
irregular-plurals@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-3.2.0.tgz#b19c490a0723798db51b235d7e39add44dab0822"
integrity sha512-YqTdPLfwP7YFN0SsD3QUVCkm9ZG2VzOXv3DOrw5G5mkMbVwptTwVcFv7/C0vOpBmgTxAeTG19XpUs1E522LW9Q==
is-absolute-url@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
@ -3690,6 +3723,13 @@ log-symbols@^3.0.0:
dependencies:
chalk "^2.4.2"
log-symbols@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
dependencies:
chalk "^4.0.0"
log-update@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708"
@ -3838,6 +3878,25 @@ meow@^6.0.0:
type-fest "^0.13.1"
yargs-parser "^18.1.3"
meow@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/meow/-/meow-7.0.1.tgz#1ed4a0a50b3844b451369c48362eb0515f04c1dc"
integrity sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==
dependencies:
"@types/minimist" "^1.2.0"
arrify "^2.0.1"
camelcase "^6.0.0"
camelcase-keys "^6.2.2"
decamelize-keys "^1.1.0"
hard-rejection "^2.1.0"
minimist-options "^4.0.2"
normalize-package-data "^2.5.0"
read-pkg-up "^7.0.1"
redent "^3.0.0"
trim-newlines "^3.0.0"
type-fest "^0.13.1"
yargs-parser "^18.1.3"
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@ -4742,6 +4801,13 @@ pkg-up@^3.1.0:
dependencies:
find-up "^3.0.0"
plur@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84"
integrity sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==
dependencies:
irregular-plurals "^3.2.0"
pnp-webpack-plugin@^1.6.4:
version "1.6.4"
resolved "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
@ -6128,7 +6194,7 @@ string-width@^3.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^4.0.0, string-width@^4.1.0:
string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
@ -6483,6 +6549,18 @@ tsconfig-paths@^3.9.0:
minimist "^1.2.0"
strip-bom "^3.0.0"
tsd@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.13.1.tgz#d2a8baa80b8319dafea37fbeb29fef3cec86e92b"
integrity sha512-+UYM8LRG/M4H8ISTg2ow8SWi65PS7Os+4DUnyiQLbJysXBp2DEmws9SMgBH+m8zHcJZqUJQ+mtDWJXP1IAvB2A==
dependencies:
eslint-formatter-pretty "^4.0.0"
globby "^11.0.1"
meow "^7.0.1"
path-exists "^4.0.0"
read-pkg-up "^7.0.0"
update-notifier "^4.1.0"
tslib@^1.8.1, tslib@^1.9.0:
version "1.13.0"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
@ -6644,7 +6722,7 @@ upath@^1.1.1:
resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
update-notifier@^4.0.0:
update-notifier@^4.0.0, update-notifier@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3"
integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==