Skip to content

Commit 3d16837

Browse files
authored
Merge pull request #5 from Astrotomic/issue-3
#3 add ability to replace multiple emojis in plain text
2 parents f6cedad + 01fedd4 commit 3d16837

23 files changed

+270
-27
lines changed

β€ŽREADME.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ composer require astrotomic/php-twemoji
2121

2222
## Usage
2323

24+
### Single Emojis
25+
26+
You can use the `Twemoji::emoji()` method to get the Twemoji image URL for a single emoji.
27+
2428
```php
2529
use Astrotomic\Twemoji\Twemoji;
2630

@@ -34,6 +38,39 @@ Twemoji::emoji('πŸŽ‰')->base('https://twemoji.astrotomic.info')->url();
3438
// https://twemoji.astrotomic.info/svg/1f389.svg
3539
```
3640

41+
### Multiple Emojis in Text
42+
43+
If you have a text and want to replace all emojis with Twemoji image tags (Markdown or HTML) you can use the `Twemoji::text()` method.
44+
This isn't aware of emojis in attributes or anything - it just finds and replaces all Emojis in the given string.
45+
46+
```php
47+
use Astrotomic\Twemoji\Twemoji;
48+
49+
Twemoji::text("Hello πŸ‘‹πŸΏ")->toMarkdown();
50+
// Hello ![πŸ‘‹πŸΏ](https://twemoji.maxcdn.com/v/latest/svg/1f44b-1f3ff.svg)
51+
52+
Twemoji::text("Hello πŸ‘‹πŸΏ")->png()->toMarkdown();
53+
// Hello ![πŸ‘‹πŸΏ](https://twemoji.maxcdn.com/v/latest/72x72/1f44b-1f3ff.png)
54+
```
55+
56+
In case you want to configure the replacer once and bind it to your container for example you can do that as well.
57+
58+
```php
59+
use Astrotomic\Twemoji\Replacer;
60+
61+
$replacer = (new Replacer())->png();
62+
63+
$replacer->text("Hello πŸ‘‹πŸΏ")->toMarkdown();
64+
// Hello ![πŸ‘‹πŸΏ](https://twemoji.maxcdn.com/v/latest/72x72/1f44b-1f3ff.png)
65+
```
66+
67+
You can also override the replacer configuration for the specific replace operation without altering the replacer configuration.
68+
69+
```php
70+
$replacer->text("Hello πŸ‘‹πŸΏ")->svg()->toMarkdown();
71+
// Hello ![πŸ‘‹πŸΏ](https://twemoji.maxcdn.com/v/latest/svg/1f44b-1f3ff.svg)
72+
```
73+
3774
## Testing
3875

3976
```bash

β€Žcomposer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
},
2424
"require-dev": {
2525
"pestphp/pest": "^0.3.0",
26-
"spatie/emoji": "^2.2.0"
26+
"spatie/emoji": "^2.3.0",
27+
"spatie/pest-plugin-snapshots": "^1.0"
2728
},
2829
"suggest": {
2930
"spatie/emoji": "*"

β€Žsrc/Concerns/Configurable.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Astrotomic\Twemoji\Concerns;
4+
5+
use Astrotomic\Twemoji\Twemoji;
6+
7+
trait Configurable
8+
{
9+
protected string $type = Twemoji::SVG;
10+
11+
protected string $base = 'https://twemoji.maxcdn.com/v/latest';
12+
13+
public function base(string $base): self
14+
{
15+
$this->base = rtrim($base, '/');
16+
17+
return $this;
18+
}
19+
20+
public function type(string $type): self
21+
{
22+
$this->type = $type;
23+
24+
return $this;
25+
}
26+
27+
public function svg(): self
28+
{
29+
$this->type = Twemoji::SVG;
30+
31+
return $this;
32+
}
33+
34+
public function png(): self
35+
{
36+
$this->type = Twemoji::PNG;
37+
38+
return $this;
39+
}
40+
}

β€Žsrc/EmojiText.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace Astrotomic\Twemoji;
4+
5+
use Astrotomic\Twemoji\Concerns\Configurable;
6+
use Closure;
7+
8+
/**
9+
* @internal
10+
*/
11+
class EmojiText
12+
{
13+
use Configurable;
14+
15+
protected string $text;
16+
17+
public function __construct(string $text)
18+
{
19+
$this->text = $text;
20+
}
21+
22+
public function toMarkdown(?Closure $alt = null): string
23+
{
24+
return $this->replace('![%{alt}](%{src})', $alt);
25+
}
26+
27+
public function toHtml(?Closure $alt = null, array $attributes = []): string
28+
{
29+
$attributes = array_merge([
30+
'width' => 72,
31+
'height' => 72,
32+
'loading' => 'lazy',
33+
'class' => 'twemoji',
34+
], $attributes);
35+
36+
$attrs = implode(' ', array_map(
37+
fn (string $key, string $value): string => "{$key}=\"{$value}\"",
38+
array_keys($attributes),
39+
array_values($attributes)
40+
));
41+
42+
return $this->replace('<img src="%{src}" alt="%{alt}" '.$attrs.' />', $alt);
43+
}
44+
45+
protected function replace(string $replacement, ?Closure $alt = null): string
46+
{
47+
$text = $this->text;
48+
49+
$text = preg_replace_callback(
50+
$this->regexp(),
51+
fn (array $matches): string => str_replace(
52+
['%{alt}', '%{src}'],
53+
[
54+
$alt
55+
? $alt($matches[0])
56+
: $matches[0],
57+
Twemoji::emoji($matches[0])
58+
->base($this->base)
59+
->type($this->type)
60+
->url(),
61+
],
62+
$replacement
63+
),
64+
$text
65+
);
66+
67+
return $text;
68+
}
69+
70+
protected function regexp(): string
71+
{
72+
return '/(?:'.json_decode(file_get_contents(dirname(__FILE__).'/regexp.json')).')/u';
73+
}
74+
}

β€Žsrc/Replacer.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Astrotomic\Twemoji;
4+
5+
use Astrotomic\Twemoji\Concerns\Configurable;
6+
7+
class Replacer
8+
{
9+
use Configurable;
10+
11+
public function text(string $text): EmojiText
12+
{
13+
return (new EmojiText($text))
14+
->base($this->base)
15+
->type($this->type);
16+
}
17+
}

β€Žsrc/Twemoji.php

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@
22

33
namespace Astrotomic\Twemoji;
44

5+
use Astrotomic\Twemoji\Concerns\Configurable;
56
use JsonSerializable;
67

78
class Twemoji implements JsonSerializable
89
{
9-
protected const SVG = 'svg';
10-
protected const PNG = 'png';
10+
use Configurable;
11+
12+
public const SVG = 'svg';
13+
public const PNG = 'png';
1114

1215
/** @var string[] */
1316
protected array $codepoints;
1417

15-
protected string $type = self::SVG;
16-
17-
protected string $base = 'https://twemoji.maxcdn.com/v/latest';
18-
1918
/**
2019
* @param string[] $codepoints
2120
*/
@@ -38,25 +37,9 @@ public static function emoji(string $emoji): self
3837
return new static($normalized);
3938
}
4039

41-
public function base(string $base): self
42-
{
43-
$this->base = rtrim($base, '/');
44-
45-
return $this;
46-
}
47-
48-
public function svg(): self
49-
{
50-
$this->type = self::SVG;
51-
52-
return $this;
53-
}
54-
55-
public function png(): self
40+
public static function text(string $text): EmojiText
5641
{
57-
$this->type = self::PNG;
58-
59-
return $this;
42+
return new EmojiText($text);
6043
}
6144

6245
public function url(): string

β€Žsrc/regexp.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

β€Žtests/Datasets/SpatieEmojis.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
use Spatie\Emoji\Emoji;
44

55
dataset('spatie-emojis', function () {
6-
$class = new ReflectionClass(Emoji::class);
7-
foreach ($class->getConstants() as $name => $emoji) {
6+
foreach (Emoji::all() as $name => $emoji) {
87
yield [$emoji, $name];
98
}
109
});

β€Žtests/Unit/ReplacerTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
use Astrotomic\Twemoji\Replacer;
4+
use function Spatie\Snapshots\assertMatchesTextSnapshot;
5+
6+
it('can replace emojis in plain text to markdown', function () {
7+
$replacer = new Replacer();
8+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->toMarkdown());
9+
});
10+
11+
it('can replace emojis in plain text to markdown using png', function () {
12+
$replacer = new Replacer();
13+
$replacer->png();
14+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->toMarkdown());
15+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸŽ‰πŸš€")->toMarkdown());
16+
});
17+
18+
it('can replace emojis in plain text to markdown using png once', function () {
19+
$replacer = new Replacer();
20+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->png()->toMarkdown());
21+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸŽ‰πŸš€")->toMarkdown());
22+
});
23+
24+
it('can replace emojis in plain text to html', function () {
25+
$replacer = new Replacer();
26+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->toHtml());
27+
});
28+
29+
it('can replace emojis in plain text to html using png', function () {
30+
$replacer = new Replacer();
31+
$replacer->png();
32+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->toHtml());
33+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸŽ‰πŸš€")->toHtml());
34+
});
35+
36+
it('can replace emojis in plain text to html using png once', function () {
37+
$replacer = new Replacer();
38+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->png()->toHtml());
39+
assertMatchesTextSnapshot($replacer->text("Hello \u{1F44B},\nEmojis are so cool! πŸŽ‰πŸš€")->toHtml());
40+
});
41+
42+
it('can replace multi codepoint emojis in plain text', function () {
43+
$replacer = new Replacer();
44+
assertMatchesTextSnapshot($replacer->text(implode(PHP_EOL, [
45+
'Hello πŸ‘‹',
46+
'Hello πŸ‘‹πŸ»',
47+
'Hello πŸ‘‹πŸΌ',
48+
'Hello πŸ‘‹πŸ½',
49+
'Hello πŸ‘‹πŸΎ',
50+
'Hello πŸ‘‹πŸΏ',
51+
]))->toMarkdown());
52+
});

β€Žtests/Unit/TwemojiTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use Astrotomic\Twemoji\Twemoji;
44
use function PHPUnit\Framework\assertEquals;
55
use function PHPUnit\Framework\assertMatchesRegularExpression;
6+
use function Spatie\Snapshots\assertMatchesTextSnapshot;
67

78
it('can generate url', function (string $emoji, string $twemoji) {
89
assertEquals(
@@ -38,3 +39,11 @@
3839
Twemoji::emoji($emoji)->url()
3940
);
4041
})->with('spatie-emojis');
42+
43+
it('can replace emojis in plain text to markdown', function () {
44+
assertMatchesTextSnapshot(Twemoji::text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->toMarkdown());
45+
});
46+
47+
it('can replace emojis in plain text to html', function () {
48+
assertMatchesTextSnapshot(Twemoji::text("Hello \u{1F44B},\nEmojis are so cool! πŸš€πŸŽ‰")->toHtml());
49+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello <img src="https://twemoji.maxcdn.com/v/latest/svg/1f44b.svg" alt="πŸ‘‹" width="72" height="72" loading="lazy" class="twemoji" />,
2+
Emojis are so cool! <img src="https://twemoji.maxcdn.com/v/latest/svg/1f680.svg" alt="πŸš€" width="72" height="72" loading="lazy" class="twemoji" /><img src="https://twemoji.maxcdn.com/v/latest/svg/1f389.svg" alt="πŸŽ‰" width="72" height="72" loading="lazy" class="twemoji" />
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello <img src="https://twemoji.maxcdn.com/v/latest/72x72/1f44b.png" alt="πŸ‘‹" width="72" height="72" loading="lazy" class="twemoji" />,
2+
Emojis are so cool! <img src="https://twemoji.maxcdn.com/v/latest/72x72/1f680.png" alt="πŸš€" width="72" height="72" loading="lazy" class="twemoji" /><img src="https://twemoji.maxcdn.com/v/latest/72x72/1f389.png" alt="πŸŽ‰" width="72" height="72" loading="lazy" class="twemoji" />
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello <img src="https://twemoji.maxcdn.com/v/latest/72x72/1f44b.png" alt="πŸ‘‹" width="72" height="72" loading="lazy" class="twemoji" />,
2+
Emojis are so cool! <img src="https://twemoji.maxcdn.com/v/latest/72x72/1f389.png" alt="πŸŽ‰" width="72" height="72" loading="lazy" class="twemoji" /><img src="https://twemoji.maxcdn.com/v/latest/72x72/1f680.png" alt="πŸš€" width="72" height="72" loading="lazy" class="twemoji" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello <img src="https://twemoji.maxcdn.com/v/latest/72x72/1f44b.png" alt="πŸ‘‹" width="72" height="72" loading="lazy" class="twemoji" />,
2+
Emojis are so cool! <img src="https://twemoji.maxcdn.com/v/latest/72x72/1f680.png" alt="πŸš€" width="72" height="72" loading="lazy" class="twemoji" /><img src="https://twemoji.maxcdn.com/v/latest/72x72/1f389.png" alt="πŸŽ‰" width="72" height="72" loading="lazy" class="twemoji" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello <img src="https://twemoji.maxcdn.com/v/latest/svg/1f44b.svg" alt="πŸ‘‹" width="72" height="72" loading="lazy" class="twemoji" />,
2+
Emojis are so cool! <img src="https://twemoji.maxcdn.com/v/latest/svg/1f389.svg" alt="πŸŽ‰" width="72" height="72" loading="lazy" class="twemoji" /><img src="https://twemoji.maxcdn.com/v/latest/svg/1f680.svg" alt="πŸš€" width="72" height="72" loading="lazy" class="twemoji" />
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello ![πŸ‘‹](https://twemoji.maxcdn.com/v/latest/svg/1f44b.svg),
2+
Emojis are so cool! ![πŸš€](https://twemoji.maxcdn.com/v/latest/svg/1f680.svg)![πŸŽ‰](https://twemoji.maxcdn.com/v/latest/svg/1f389.svg)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello ![πŸ‘‹](https://twemoji.maxcdn.com/v/latest/72x72/1f44b.png),
2+
Emojis are so cool! ![πŸš€](https://twemoji.maxcdn.com/v/latest/72x72/1f680.png)![πŸŽ‰](https://twemoji.maxcdn.com/v/latest/72x72/1f389.png)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello ![πŸ‘‹](https://twemoji.maxcdn.com/v/latest/72x72/1f44b.png),
2+
Emojis are so cool! ![πŸŽ‰](https://twemoji.maxcdn.com/v/latest/72x72/1f389.png)![πŸš€](https://twemoji.maxcdn.com/v/latest/72x72/1f680.png)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello ![πŸ‘‹](https://twemoji.maxcdn.com/v/latest/72x72/1f44b.png),
2+
Emojis are so cool! ![πŸš€](https://twemoji.maxcdn.com/v/latest/72x72/1f680.png)![πŸŽ‰](https://twemoji.maxcdn.com/v/latest/72x72/1f389.png)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello ![πŸ‘‹](https://twemoji.maxcdn.com/v/latest/svg/1f44b.svg),
2+
Emojis are so cool! ![πŸŽ‰](https://twemoji.maxcdn.com/v/latest/svg/1f389.svg)![πŸš€](https://twemoji.maxcdn.com/v/latest/svg/1f680.svg)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Hello ![πŸ‘‹](https://twemoji.maxcdn.com/v/latest/svg/1f44b.svg)
2+
Hello ![πŸ‘‹πŸ»](https://twemoji.maxcdn.com/v/latest/svg/1f44b-1f3fb.svg)
3+
Hello ![πŸ‘‹πŸΌ](https://twemoji.maxcdn.com/v/latest/svg/1f44b-1f3fc.svg)
4+
Hello ![πŸ‘‹πŸ½](https://twemoji.maxcdn.com/v/latest/svg/1f44b-1f3fd.svg)
5+
Hello ![πŸ‘‹πŸΎ](https://twemoji.maxcdn.com/v/latest/svg/1f44b-1f3fe.svg)
6+
Hello ![πŸ‘‹πŸΏ](https://twemoji.maxcdn.com/v/latest/svg/1f44b-1f3ff.svg)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello <img src="https://twemoji.maxcdn.com/v/latest/svg/1f44b.svg" alt="πŸ‘‹" width="72" height="72" loading="lazy" class="twemoji" />,
2+
Emojis are so cool! <img src="https://twemoji.maxcdn.com/v/latest/svg/1f680.svg" alt="πŸš€" width="72" height="72" loading="lazy" class="twemoji" /><img src="https://twemoji.maxcdn.com/v/latest/svg/1f389.svg" alt="πŸŽ‰" width="72" height="72" loading="lazy" class="twemoji" />
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hello ![πŸ‘‹](https://twemoji.maxcdn.com/v/latest/svg/1f44b.svg),
2+
Emojis are so cool! ![πŸš€](https://twemoji.maxcdn.com/v/latest/svg/1f680.svg)![πŸŽ‰](https://twemoji.maxcdn.com/v/latest/svg/1f389.svg)

0 commit comments

Comments
Β (0)