Skip to content

Commit a04e3e7

Browse files
committed
Update CMS media link extension and related components
Refactors and updates the CMS media link extension JavaScript, Blade view, and PHP classes for the Markdown and Rich Editors. These changes improve integration and maintain consistency across the editor plugins and their extensions.
1 parent 5e8d1aa commit a04e3e7

6 files changed

Lines changed: 235 additions & 71 deletions

File tree

resources/dist/components/rich-content-plugins/extension-cms-media-link.js

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/js/components/rich-content-plugins/extension-cms-media-link.js

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
import { mergeAttributes, Node, Extension } from '@tiptap/core'
22

3+
const convertResponsiveAttributes = (element) => {
4+
const responsiveData = {}
5+
// Ensure the element is an object, key-value pairs, key is breakpoint, value is URL
6+
// If it's an array, convert to object
7+
if (Array.isArray(element)) {
8+
element.forEach((item) => {
9+
const { breakpoint, url } = item
10+
responsiveData[`data-responsive_${breakpoint}`] = url
11+
})
12+
}
13+
// If it's already an object
14+
else if (typeof element === 'object' && element !== null) {
15+
Object.keys(element).forEach((breakpoint) => {
16+
responsiveData[`data-responsive__${breakpoint}`] =
17+
element[breakpoint]
18+
})
19+
}
20+
return responsiveData
21+
}
22+
323
export default Node.create({
424
name: 'cmsMediaAsset',
525

@@ -12,10 +32,10 @@ export default Node.create({
1232
id: {
1333
default: null,
1434
parseHTML: (element) =>
15-
element.getAttribute('data-mediaasset-id'),
35+
element.getAttribute('data-cmsmediaasset-id'),
1636
renderHTML: (attributes) => {
1737
if (!attributes.id) return {}
18-
return { 'data-mediaasset-id': attributes.id }
38+
return { 'data-cmsmediaasset-id': attributes.id }
1939
},
2040
},
2141
url: {
@@ -59,6 +79,14 @@ export default Node.create({
5979
return { 'data-filename': attributes.filename }
6080
},
6181
},
82+
responsive: {
83+
default: [],
84+
renderHTML: (attributes) => {
85+
return convertResponsiveAttributes(
86+
attributes.responsive || [],
87+
)
88+
},
89+
},
6290
}
6391
},
6492

@@ -71,20 +99,35 @@ export default Node.create({
7199
},
72100

73101
renderHTML({ HTMLAttributes, node }) {
74-
const { thumbnailUrl, title, url, mimeType, filename } = node.attrs
102+
const {
103+
title,
104+
url,
105+
mimeType,
106+
filename,
107+
thumbnailUrl = null,
108+
} = node.attrs
109+
110+
const getType = () => {
111+
if (mimeType) {
112+
if (mimeType.startsWith('image/')) {
113+
return 'img'
114+
} else if (mimeType.startsWith('video/')) {
115+
return 'video'
116+
} else if (mimeType.startsWith('audio/')) {
117+
return 'audio'
118+
}
119+
}
120+
return 'file'
121+
}
75122

76123
// Build the inner content as proper HTML elements
77124
let innerContent
78-
if (
79-
thumbnailUrl &&
80-
mimeType.startsWith('image/') &&
81-
!filename.endsWith('.svg')
82-
) {
125+
if (getType(mimeType) === 'img') {
83126
// Create img element
84127
innerContent = [
85128
'img',
86129
{
87-
src: thumbnailUrl,
130+
src: thumbnailUrl ?? url,
88131
alt: title || 'Media Asset',
89132
},
90133
]
@@ -93,15 +136,33 @@ export default Node.create({
93136
innerContent = title || 'Media Asset'
94137
}
95138

139+
let mediaTypeAttribute = {}
140+
if (getType(mimeType) !== 'img') {
141+
mediaTypeAttribute = { style: 'text-decoration: underline;' }
142+
} else {
143+
if (!filename.endsWith('.svg')) {
144+
mediaTypeAttribute = {
145+
...convertResponsiveAttributes(node.attrs.responsive || []),
146+
}
147+
}
148+
}
149+
96150
return [
97151
'span',
98-
mergeAttributes(HTMLAttributes, {
99-
class: 'trix-attachment-mediapicker',
100-
'data-mediaasset-id': node.attrs.id,
101-
'data-url': url,
102-
'data-mime-type': mimeType,
103-
'data-filename': filename,
104-
}),
152+
mergeAttributes(
153+
HTMLAttributes,
154+
{
155+
class: 'trix-attachment-mediapicker',
156+
'data-mediaasset-id': node.attrs.id,
157+
'data-url': url,
158+
'data-mime-type': mimeType,
159+
'data-filename': filename,
160+
},
161+
mediaTypeAttribute,
162+
thumbnailUrl && {
163+
'data-thumbnail-url': thumbnailUrl,
164+
},
165+
),
105166
innerContent,
106167
]
107168
},

resources/views/filament/forms/components/markdown-editor.blade.php

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -150,35 +150,19 @@
150150
if (!attributes || typeof attributes !== 'string' || attributes.trim() === '') {
151151
return '';
152152
}
153-
153+
154+
// markdown attribute format
154155
return `{${attributes}}`;
155156
};
156157
157-
// Helper function to convert item object to markdown
158-
const itemToMarkdown = (item) => {
159-
if (typeof item === 'string') {
160-
return item; // Fallback for old format
161-
}
162-
163-
const { url, title, tag, attributes } = item;
164-
let markdown = '';
165-
166-
if (tag === 'img') {
167-
markdown = `![${title}](${url})`;
168-
} else {
169-
markdown = `[${title}](${url})`;
170-
}
171-
172-
// Add attributes using helper function
173-
markdown += formatAttributes(attributes);
174-
175-
return markdown;
176-
};
177-
178158
if (pickerName === 'contentPicker' && selectedText && dataArray.length > 0) {
179159
// For contentPicker with selected text, toggle as link
180160
const item = dataArray[0];
181-
const { url, attributes } = item;
161+
162+
console.log('item for link', item);
163+
164+
console.log('item for link', item);
165+
const { url, attributes = '' } = item;
182166
183167
let linkMarkdown = `[${selectedText}](${url})`;
184168
@@ -192,10 +176,39 @@
192176
);
193177
} else {
194178
// Default behavior: insert the content
195-
const markdownItems = dataArray.map(itemToMarkdown);
179+
const markdownItems = dataArray.map((item) => {
180+
181+
const { url, title, type, attributes = '', content = null } = item;
182+
183+
let markdown = '';
184+
if (content?.length > 0) {
185+
markdown = content || '';
186+
} else {
187+
if (type === 'img') {
188+
markdown = `![${title}](${url})`;
189+
} else {
190+
markdown = `[${title}](${url})`;
191+
}
192+
193+
// Add attributes
194+
markdown += formatAttributes(attributes);
195+
}
196+
197+
return markdown;
198+
});
199+
200+
let content;
201+
if (pickerName === 'mediaPicker') {
202+
// For media picker, check if we have videos - they need line breaks
203+
const hasVideos = dataArray.some(item => item.type === 'video');
204+
content = hasVideos ? markdownItems.join('\r\n\r\n') : markdownItems.join(' ');
205+
} else {
206+
// For content picker and others, join with white space
207+
content = markdownItems.join(' ');
208+
}
196209
197210
cm.replaceRange(
198-
markdownItems.join(' '),
211+
content,
199212
startPoint,
200213
endPoint,
201214
);

src/Filament/Forms/Components/MarkdownEditor.php

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,68 @@ protected function mutateMediaPickerState(Model | MediaAsset $mediaAsset)
165165
/** @var null | Media */
166166
$media = $mediaAsset->getFirstMedia();
167167
$mediaUrl = $mediaAsset->getUrl(isAbsolute: false);
168-
$title = $media?->title ?? $mediaAsset->title;
168+
$thumbnail = $mediaAsset->getThumbnailUrl(isAbsolute: false);
169+
$title = $media?->file_name ?? $media?->title ?? $mediaAsset->title;
170+
$mimeType = $media?->mime_type;
169171

170172
$attributes = [
171173
'data-cmsmediaasset-id' => $mediaAsset->getKey(),
174+
'data-media-mime-type' => $mimeType,
172175
];
173176

177+
// Determine media type and tag
178+
$type = 'link'; // Default to link
179+
180+
if ($mediaAsset->isImage()) {
181+
$type = 'img';
182+
183+
$responsive = collect($mediaAsset->getResponsiveImages(isAbsolute: false))
184+
->flatten(1)
185+
->pluck('url', 'width')
186+
->each(function ($url, $width) use (&$attributes) {
187+
$attributes["data-image-responsive__{$width}"] = $url;
188+
});
189+
190+
$attributes['data-media-thumbnail'] = $thumbnail;
191+
}
192+
// Video handling
193+
elseif ($mimeType && str_starts_with($mimeType, 'video/')) {
194+
$type = 'video';
195+
196+
// Add video-specific attributes
197+
$attributes['data-media-type'] = 'video';
198+
$attributes['controls'] = 'controls';
199+
$attributes['preload'] = 'metadata';
200+
201+
$attributes['data-media-thumbnail'] = $thumbnail;
202+
}
203+
// Audio handling
204+
elseif ($mimeType && str_starts_with($mimeType, 'audio/')) {
205+
$type = 'audio';
206+
207+
// Add audio-specific attributes
208+
$attributes['data-media-type'] = 'audio';
209+
$attributes['controls'] = 'controls';
210+
$attributes['preload'] = 'metadata';
211+
}
212+
174213
// Convert attributes array to string
175214
$attributesString = collect($attributes)
176-
->map(fn($value, $key) => "{$key}=\"{$value}\"")
215+
->filter(fn($value) => !is_null($value) && $value !== '')
216+
->map(fn($value, $key) => in_array($value, ['controls', 'metadata']) ? $key : "{$key}=\"{$value}\"")
177217
->join(' ');
178218

219+
$content = match ($type) {
220+
'video' => "<video src=\"{$mediaUrl}\" {$attributesString}></video>",
221+
'audio' => "<audio src=\"{$mediaUrl}\" {$attributesString}></audio>",
222+
default => null,
223+
};
224+
179225
return [
180226
'url' => $mediaUrl,
181227
'title' => $title,
182-
'tag' => $mediaAsset->isImage() ? 'img' : 'a',
228+
'type' => $type,
229+
'content' => $content,
183230
'attributes' => $attributesString,
184231
];
185232
}
@@ -229,7 +276,7 @@ protected function mutateContentPickerState(Model | Content $content)
229276
return [
230277
'url' => $relativeUrl,
231278
'title' => $content->title,
232-
'tag' => 'a',
279+
'type' => 'link',
233280
'attributes' => $attributesString,
234281
];
235282
}

src/Filament/Forms/Components/RichEditor/Plugins/MediaPickerRichPlugin.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ protected function mutateMediaPickerState(Model | MediaAsset $mediaAsset)
130130
$media = $mediaAsset->getFirstMedia();
131131
$mediaUrl = $mediaAsset->getUrl(isAbsolute: false);
132132
$thumbnailUrl = $mediaAsset->getThumbnailUrl(isAbsolute: false);
133-
$title = $media?->title ?? $mediaAsset->title;
133+
$title = $media?->file_name ?? $media?->title ?? $mediaAsset->title;
134134

135135
return [
136136
'id' => $mediaAsset->getKey(),
@@ -139,6 +139,12 @@ protected function mutateMediaPickerState(Model | MediaAsset $mediaAsset)
139139
'title' => $title,
140140
'filename' => $media?->file_name,
141141
'mimeType' => $media?->mime_type,
142+
... ($mediaAsset->isImage() ? [
143+
'responsive' => collect($mediaAsset->getResponsiveImages(isAbsolute: false))
144+
->flatten(1)
145+
->pluck('url', 'width')
146+
->all(),
147+
] : []),
142148
];
143149
}
144150
}

0 commit comments

Comments
 (0)