feat: edit content directly in previews
This commit is contained in:
@@ -22,6 +22,10 @@ The editor should use one shared content body for every selected target channel,
|
|||||||
- Make target preview tabs compact.
|
- Make target preview tabs compact.
|
||||||
- Move the create content entry point into the top app menu bar.
|
- Move the create content entry point into the top app menu bar.
|
||||||
- Remove the asset-management tab from the content detail production panel.
|
- Remove the asset-management tab from the content detail production panel.
|
||||||
|
- Move content editing into the channel preview cards instead of using a separate post text field.
|
||||||
|
- Keep target copy/title synchronized by default, with per-target opt-out.
|
||||||
|
- Show title editing only for networks that normally need titles, such as YouTube, Reddit, Website, and newsletters.
|
||||||
|
- Let each target choose its own media requirement.
|
||||||
- Preserve existing save payloads and backend contracts.
|
- Preserve existing save payloads and backend contracts.
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
@@ -44,3 +48,7 @@ npm run build
|
|||||||
- [x] Target channel tabs use compact icon-only buttons with channel names as accessible labels and tooltips.
|
- [x] Target channel tabs use compact icon-only buttons with channel names as accessible labels and tooltips.
|
||||||
- [x] Create content is available from the top app menu bar.
|
- [x] Create content is available from the top app menu bar.
|
||||||
- [x] Assets are no longer shown in the content detail production panel.
|
- [x] Assets are no longer shown in the content detail production panel.
|
||||||
|
- [x] Authors edit the active channel preview directly.
|
||||||
|
- [x] Channel copy/title is synchronized by default and can be unsynchronized per target.
|
||||||
|
- [x] Title input only appears for title-oriented targets.
|
||||||
|
- [x] Each target can choose no media, image, video, clip, or carousel independently.
|
||||||
|
|||||||
@@ -96,8 +96,27 @@
|
|||||||
const selectedPlacements = computed(() =>
|
const selectedPlacements = computed(() =>
|
||||||
form.placements.filter(placement => placement.channelId || placement.channelName || placement.network)
|
form.placements.filter(placement => placement.channelId || placement.channelName || placement.network)
|
||||||
);
|
);
|
||||||
|
const primaryPublicationMessage = computed(() => {
|
||||||
|
const syncedMessage = selectedPlacements.value
|
||||||
|
.find(placement => isPlacementSynced(placement) && placement.message?.trim())
|
||||||
|
?.message;
|
||||||
|
const firstPlacementMessage = selectedPlacements.value
|
||||||
|
.find(placement => placement.message?.trim())
|
||||||
|
?.message;
|
||||||
|
|
||||||
|
return form.body.trim() || syncedMessage?.trim() || firstPlacementMessage?.trim() || '';
|
||||||
|
});
|
||||||
const derivedTitle = computed(() => {
|
const derivedTitle = computed(() => {
|
||||||
const firstLine = form.body
|
const explicitTitle = form.title.trim() || selectedPlacements.value
|
||||||
|
.find(placement => placement.title?.trim())
|
||||||
|
?.title
|
||||||
|
?.trim();
|
||||||
|
|
||||||
|
if (explicitTitle) {
|
||||||
|
return explicitTitle.length > 80 ? `${explicitTitle.slice(0, 77)}...` : explicitTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstLine = primaryPublicationMessage.value
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(line => line.trim())
|
.map(line => line.trim())
|
||||||
.find(Boolean);
|
.find(Boolean);
|
||||||
@@ -116,7 +135,6 @@
|
|||||||
return selectedPlacements.value.find(placement => placement.id === activePlacementId.value)
|
return selectedPlacements.value.find(placement => placement.id === activePlacementId.value)
|
||||||
?? selectedPlacements.value[0];
|
?? selectedPlacements.value[0];
|
||||||
});
|
});
|
||||||
const sharedPreviewText = computed(() => form.body.trim() || 'Draft the shared caption once. Every selected channel will preview this same content.');
|
|
||||||
const sharedPreviewTags = computed(() => parseHashtags(form.hashtags));
|
const sharedPreviewTags = computed(() => parseHashtags(form.hashtags));
|
||||||
const workspaceHashtagFeed = computed(() => {
|
const workspaceHashtagFeed = computed(() => {
|
||||||
const selected = new Set(sharedPreviewTags.value.map(tag => normalizeHashtag(tag).toLowerCase()));
|
const selected = new Set(sharedPreviewTags.value.map(tag => normalizeHashtag(tag).toLowerCase()));
|
||||||
@@ -218,6 +236,9 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
const activePlacementUsesTitle = computed(() =>
|
||||||
|
activePlacement.value ? placementUsesTitle(activePlacement.value) : false
|
||||||
|
);
|
||||||
|
|
||||||
function blankPlacement(channel = null) {
|
function blankPlacement(channel = null) {
|
||||||
return {
|
return {
|
||||||
@@ -226,28 +247,15 @@
|
|||||||
channelId: channel?.id ?? '',
|
channelId: channel?.id ?? '',
|
||||||
channelName: channel?.name ?? '',
|
channelName: channel?.name ?? '',
|
||||||
variantLabel: '',
|
variantLabel: '',
|
||||||
|
title: form.title,
|
||||||
message: form.body,
|
message: form.body,
|
||||||
|
isSynced: true,
|
||||||
hashtags: form.hashtags,
|
hashtags: form.hashtags,
|
||||||
|
mediaKind: defaultMediaKind(channel?.network ?? ''),
|
||||||
mediaItems: [],
|
mediaItems: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function blankMedia() {
|
|
||||||
return {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
mediaType: 'Image',
|
|
||||||
label: '',
|
|
||||||
url: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncPlacementChannel(placement, value) {
|
|
||||||
const channel = availableChannels.value.find(candidate => candidate.id === value);
|
|
||||||
placement.channelId = value;
|
|
||||||
placement.channelName = channel?.name ?? '';
|
|
||||||
placement.network = channel?.network ?? placement.network;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPlacement(channel = null) {
|
function addPlacement(channel = null) {
|
||||||
if (channel) {
|
if (channel) {
|
||||||
const existing = form.placements.find(placement => placement.channelId === channel.id);
|
const existing = form.placements.find(placement => placement.channelId === channel.id);
|
||||||
@@ -288,12 +296,88 @@
|
|||||||
return form.placements.some(placement => placement.channelId === channelId);
|
return form.placements.some(placement => placement.channelId === channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMedia(placement) {
|
function isPlacementSynced(placement) {
|
||||||
placement.mediaItems.push(blankMedia());
|
return placement?.isSynced !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeMedia(placement, mediaId) {
|
function updatePlacementSync(placement, value) {
|
||||||
placement.mediaItems = placement.mediaItems.filter(media => media.id !== mediaId);
|
placement.isSynced = value;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
placement.title = form.title;
|
||||||
|
placement.message = form.body;
|
||||||
|
placement.hashtags = form.hashtags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function placementMessage(placement) {
|
||||||
|
if (!placement) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return isPlacementSynced(placement) ? form.body : placement.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePlacementMessage(placement, value) {
|
||||||
|
if (isPlacementSynced(placement)) {
|
||||||
|
form.body = value;
|
||||||
|
syncSharedPlacements();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
placement.message = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function placementTitle(placement) {
|
||||||
|
if (!placement) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return isPlacementSynced(placement) ? form.title : placement.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePlacementTitle(placement, value) {
|
||||||
|
if (isPlacementSynced(placement)) {
|
||||||
|
form.title = value;
|
||||||
|
syncSharedPlacements();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
placement.title = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncSharedPlacements() {
|
||||||
|
for (const placement of form.placements) {
|
||||||
|
if (!isPlacementSynced(placement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
placement.title = form.title;
|
||||||
|
placement.message = form.body;
|
||||||
|
placement.hashtags = form.hashtags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function placementUsesTitle(placement) {
|
||||||
|
const normalized = (placement?.network ?? '').toLowerCase();
|
||||||
|
return normalized.includes('youtube') ||
|
||||||
|
normalized.includes('reddit') ||
|
||||||
|
normalized.includes('website') ||
|
||||||
|
normalized.includes('newsletter');
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultMediaKind(network) {
|
||||||
|
const normalized = (network ?? '').toLowerCase();
|
||||||
|
|
||||||
|
if (normalized.includes('youtube') || normalized.includes('tiktok')) {
|
||||||
|
return 'Video';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized.includes('instagram')) {
|
||||||
|
return 'Image';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'None';
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeHashtag(tagToRemove) {
|
function removeHashtag(tagToRemove) {
|
||||||
@@ -432,8 +516,11 @@
|
|||||||
channelId,
|
channelId,
|
||||||
channelName,
|
channelName,
|
||||||
variantLabel: placement.variantLabel ?? '',
|
variantLabel: placement.variantLabel ?? '',
|
||||||
message: form.body,
|
title: placement.title ?? form.title,
|
||||||
hashtags: form.hashtags,
|
message: placement.message ?? form.body,
|
||||||
|
isSynced: placement.isSynced !== false,
|
||||||
|
hashtags: placement.hashtags ?? form.hashtags,
|
||||||
|
mediaKind: placement.mediaKind ?? defaultMediaKind(network),
|
||||||
mediaItems: (placement.mediaItems ?? []).map(media => ({
|
mediaItems: (placement.mediaItems ?? []).map(media => ({
|
||||||
id: media.id ?? crypto.randomUUID(),
|
id: media.id ?? crypto.randomUUID(),
|
||||||
mediaType: media.mediaType ?? 'Image',
|
mediaType: media.mediaType ?? 'Image',
|
||||||
@@ -454,11 +541,7 @@
|
|||||||
body: form.body,
|
body: form.body,
|
||||||
hashtags: form.hashtags,
|
hashtags: form.hashtags,
|
||||||
changeSummary: form.changeSummary,
|
changeSummary: form.changeSummary,
|
||||||
placements: form.placements.map(placement => ({
|
placements: form.placements,
|
||||||
...placement,
|
|
||||||
message: form.body,
|
|
||||||
hashtags: form.hashtags,
|
|
||||||
})),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,8 +567,11 @@
|
|||||||
channelId: channel?.id ?? '',
|
channelId: channel?.id ?? '',
|
||||||
channelName: channel?.name ?? target,
|
channelName: channel?.name ?? target,
|
||||||
variantLabel: '',
|
variantLabel: '',
|
||||||
|
title: item.value?.title ?? '',
|
||||||
message: item.value?.publicationMessage ?? '',
|
message: item.value?.publicationMessage ?? '',
|
||||||
|
isSynced: true,
|
||||||
hashtags: item.value?.hashtags ?? '',
|
hashtags: item.value?.hashtags ?? '',
|
||||||
|
mediaKind: defaultMediaKind(channel?.network ?? ''),
|
||||||
mediaItems: [],
|
mediaItems: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -563,7 +649,9 @@
|
|||||||
async function saveContent() {
|
async function saveContent() {
|
||||||
saveError.message = '';
|
saveError.message = '';
|
||||||
|
|
||||||
if (!form.body.trim() || !form.campaignId || !form.placements.length) {
|
syncSharedPlacements();
|
||||||
|
|
||||||
|
if (!primaryPublicationMessage.value || !form.campaignId || !form.placements.length) {
|
||||||
saveError.message = 'Post text, campaign, and at least one channel are required.';
|
saveError.message = 'Post text, campaign, and at least one channel are required.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -576,7 +664,7 @@
|
|||||||
const payload = {
|
const payload = {
|
||||||
title: derivedTitle.value,
|
title: derivedTitle.value,
|
||||||
campaignId: form.campaignId,
|
campaignId: form.campaignId,
|
||||||
publicationMessage: form.body.trim(),
|
publicationMessage: primaryPublicationMessage.value,
|
||||||
publicationTargets: placementSummary.value,
|
publicationTargets: placementSummary.value,
|
||||||
hashtags: form.hashtags.trim(),
|
hashtags: form.hashtags.trim(),
|
||||||
dueDate: null,
|
dueDate: null,
|
||||||
@@ -934,16 +1022,6 @@
|
|||||||
<span>{{ placementSummary || 'No channels selected yet' }}</span>
|
<span>{{ placementSummary || 'No channels selected yet' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-grid">
|
|
||||||
<v-textarea
|
|
||||||
v-model="form.body"
|
|
||||||
class="field-wide"
|
|
||||||
label="Post text"
|
|
||||||
rows="5"
|
|
||||||
variant="outlined"
|
|
||||||
hide-details
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label class="field field-wide">
|
<label class="field field-wide">
|
||||||
<span>Shared hashtags</span>
|
<span>Shared hashtags</span>
|
||||||
<div class="hashtags-input-shell">
|
<div class="hashtags-input-shell">
|
||||||
@@ -999,7 +1077,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-section preview-editor-section">
|
<div class="content-section preview-editor-section">
|
||||||
@@ -1056,6 +1133,7 @@
|
|||||||
<div class="preview-stage">
|
<div class="preview-stage">
|
||||||
<article
|
<article
|
||||||
v-if="activePlacement"
|
v-if="activePlacement"
|
||||||
|
:key="activePlacement.id"
|
||||||
class="social-preview-card"
|
class="social-preview-card"
|
||||||
:class="`is-${previewNetworkClass(activePlacement.network)}`"
|
:class="`is-${previewNetworkClass(activePlacement.network)}`"
|
||||||
>
|
>
|
||||||
@@ -1069,27 +1147,68 @@
|
|||||||
<span>{{ previewHandle(activePlacement) }}</span>
|
<span>{{ previewHandle(activePlacement) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<v-btn variant="text" :ripple="false"
|
<div class="preview-controls">
|
||||||
class="preview-remove"
|
<label class="sync-toggle">
|
||||||
type="button"
|
<input
|
||||||
:aria-label="`Remove ${previewProfileName(activePlacement)}`"
|
type="checkbox"
|
||||||
@click="removePlacement(activePlacement.id)"
|
:checked="isPlacementSynced(activePlacement)"
|
||||||
>
|
@change="updatePlacementSync(activePlacement, $event.target.checked)"
|
||||||
<v-icon :icon="mdiClose" />
|
/>
|
||||||
</v-btn>
|
<span>Sync</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<select
|
||||||
|
v-model="activePlacement.mediaKind"
|
||||||
|
class="media-kind-select"
|
||||||
|
aria-label="Media type"
|
||||||
|
>
|
||||||
|
<option value="None">No media</option>
|
||||||
|
<option value="Image">Image</option>
|
||||||
|
<option value="Video">Video</option>
|
||||||
|
<option value="Clip">Clip</option>
|
||||||
|
<option value="Carousel">Carousel</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<v-btn variant="text" :ripple="false"
|
||||||
|
class="preview-remove"
|
||||||
|
type="button"
|
||||||
|
:aria-label="`Remove ${previewProfileName(activePlacement)}`"
|
||||||
|
@click="removePlacement(activePlacement.id)"
|
||||||
|
>
|
||||||
|
<v-icon :icon="mdiClose" />
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="previewNetworkClass(activePlacement.network) === 'youtube'"
|
v-if="previewNetworkClass(activePlacement.network) === 'youtube'"
|
||||||
class="youtube-preview"
|
class="youtube-preview"
|
||||||
>
|
>
|
||||||
<div class="youtube-player">
|
<div
|
||||||
|
v-if="activePlacement.mediaKind !== 'None'"
|
||||||
|
class="youtube-player"
|
||||||
|
>
|
||||||
<v-icon :icon="mdiYoutube" />
|
<v-icon :icon="mdiYoutube" />
|
||||||
|
<span>{{ activePlacement.mediaKind }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="youtube-meta">
|
<div class="youtube-meta">
|
||||||
<strong>{{ form.title || 'Untitled video' }}</strong>
|
<input
|
||||||
|
v-if="activePlacementUsesTitle"
|
||||||
|
class="preview-title-input"
|
||||||
|
type="text"
|
||||||
|
:value="placementTitle(activePlacement)"
|
||||||
|
placeholder="Video title"
|
||||||
|
@input="updatePlacementTitle(activePlacement, $event.target.value)"
|
||||||
|
/>
|
||||||
<span>{{ previewProfileName(activePlacement) }}</span>
|
<span>{{ previewProfileName(activePlacement) }}</span>
|
||||||
<p>{{ sharedPreviewText }}</p>
|
<div
|
||||||
|
class="preview-copy-editor"
|
||||||
|
contenteditable="true"
|
||||||
|
role="textbox"
|
||||||
|
aria-multiline="true"
|
||||||
|
data-placeholder="Write the video description..."
|
||||||
|
@input="updatePlacementMessage(activePlacement, $event.currentTarget.innerText)"
|
||||||
|
>{{ placementMessage(activePlacement) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1097,7 +1216,14 @@
|
|||||||
v-else-if="previewNetworkClass(activePlacement.network) === 'x'"
|
v-else-if="previewNetworkClass(activePlacement.network) === 'x'"
|
||||||
class="x-preview"
|
class="x-preview"
|
||||||
>
|
>
|
||||||
<p>{{ sharedPreviewText }}</p>
|
<div
|
||||||
|
class="preview-copy-editor is-x"
|
||||||
|
contenteditable="true"
|
||||||
|
role="textbox"
|
||||||
|
aria-multiline="true"
|
||||||
|
data-placeholder="Write the post..."
|
||||||
|
@input="updatePlacementMessage(activePlacement, $event.currentTarget.innerText)"
|
||||||
|
>{{ placementMessage(activePlacement) }}</div>
|
||||||
<div
|
<div
|
||||||
v-if="sharedPreviewTags.length"
|
v-if="sharedPreviewTags.length"
|
||||||
class="preview-tags"
|
class="preview-tags"
|
||||||
@@ -1121,10 +1247,29 @@
|
|||||||
v-else
|
v-else
|
||||||
class="feed-preview"
|
class="feed-preview"
|
||||||
>
|
>
|
||||||
<div class="feed-media">
|
<div
|
||||||
|
v-if="activePlacement.mediaKind !== 'None'"
|
||||||
|
class="feed-media"
|
||||||
|
>
|
||||||
<v-icon :icon="channelIcon(activePlacement.network)" />
|
<v-icon :icon="channelIcon(activePlacement.network)" />
|
||||||
|
<span>{{ activePlacement.mediaKind }}</span>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ sharedPreviewText }}</p>
|
<input
|
||||||
|
v-if="activePlacementUsesTitle"
|
||||||
|
class="preview-title-input"
|
||||||
|
type="text"
|
||||||
|
:value="placementTitle(activePlacement)"
|
||||||
|
placeholder="Post title"
|
||||||
|
@input="updatePlacementTitle(activePlacement, $event.target.value)"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="preview-copy-editor"
|
||||||
|
contenteditable="true"
|
||||||
|
role="textbox"
|
||||||
|
aria-multiline="true"
|
||||||
|
data-placeholder="Write the post..."
|
||||||
|
@input="updatePlacementMessage(activePlacement, $event.currentTarget.innerText)"
|
||||||
|
>{{ placementMessage(activePlacement) }}</div>
|
||||||
<div
|
<div
|
||||||
v-if="sharedPreviewTags.length"
|
v-if="sharedPreviewTags.length"
|
||||||
class="preview-tags"
|
class="preview-tags"
|
||||||
@@ -1652,6 +1797,29 @@
|
|||||||
@apply justify-between;
|
@apply justify-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-controls {
|
||||||
|
@apply flex flex-wrap items-center justify-end gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-toggle {
|
||||||
|
@apply inline-flex min-h-8 items-center gap-2 rounded-full border px-3 py-1 text-xs font-bold;
|
||||||
|
background: var(--app-control-subtle);
|
||||||
|
border-color: var(--app-border-subtle);
|
||||||
|
color: var(--app-color-on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-toggle input {
|
||||||
|
@apply h-4 w-4 accent-teal-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-kind-select {
|
||||||
|
@apply h-8 rounded-full border px-3 text-xs font-bold;
|
||||||
|
background: var(--app-color-on-primary);
|
||||||
|
border-color: var(--app-border-subtle);
|
||||||
|
color: var(--app-color-on-surface);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.preview-profile div:last-child {
|
.preview-profile div:last-child {
|
||||||
@apply flex min-w-0 flex-col;
|
@apply flex min-w-0 flex-col;
|
||||||
}
|
}
|
||||||
@@ -1673,6 +1841,39 @@
|
|||||||
color: var(--app-text-muted);
|
color: var(--app-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-title-input {
|
||||||
|
@apply w-full border-0 bg-transparent p-0 text-xl font-black;
|
||||||
|
color: var(--app-color-on-surface);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-title-input::placeholder {
|
||||||
|
color: var(--app-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-copy-editor {
|
||||||
|
@apply min-h-28 whitespace-pre-line rounded-[0.875rem] border p-3 text-sm leading-6;
|
||||||
|
background: rgba(248, 250, 252, 0.72);
|
||||||
|
border-color: var(--app-border-subtle);
|
||||||
|
color: var(--app-color-on-surface);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-copy-editor:focus {
|
||||||
|
border-color: rgba(15, 118, 110, 0.42);
|
||||||
|
box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-copy-editor:empty::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
color: var(--app-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-copy-editor.is-x {
|
||||||
|
@apply min-h-40 text-lg leading-8;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.youtube-preview,
|
.youtube-preview,
|
||||||
.feed-preview,
|
.feed-preview,
|
||||||
.x-preview {
|
.x-preview {
|
||||||
@@ -1685,6 +1886,13 @@
|
|||||||
color: #ef4444;
|
color: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.youtube-player span,
|
||||||
|
.feed-media span {
|
||||||
|
@apply rounded-full px-3 py-1 text-xs font-black uppercase tracking-[0.14em];
|
||||||
|
background: rgba(255, 255, 255, 0.18);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.youtube-player :deep(.v-icon) {
|
.youtube-player :deep(.v-icon) {
|
||||||
@apply text-6xl;
|
@apply text-6xl;
|
||||||
}
|
}
|
||||||
@@ -1698,29 +1906,18 @@
|
|||||||
color: var(--app-color-on-surface);
|
color: var(--app-color-on-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.youtube-meta p,
|
|
||||||
.x-preview p,
|
|
||||||
.feed-preview p {
|
|
||||||
@apply whitespace-pre-line text-sm leading-6;
|
|
||||||
color: var(--app-color-on-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.x-preview {
|
.x-preview {
|
||||||
@apply rounded-[1rem] border p-4;
|
@apply rounded-[1rem] border p-4;
|
||||||
border-color: var(--app-border-subtle);
|
border-color: var(--app-border-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.x-preview p {
|
|
||||||
@apply text-lg leading-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.x-engagement {
|
.x-engagement {
|
||||||
@apply mt-auto grid grid-cols-4 gap-2 border-t pt-3 text-center;
|
@apply mt-auto grid grid-cols-4 gap-2 border-t pt-3 text-center;
|
||||||
border-color: var(--app-border-subtle);
|
border-color: var(--app-border-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-media {
|
.feed-media {
|
||||||
@apply grid aspect-[4/5] w-full place-items-center rounded-[1rem];
|
@apply grid aspect-[4/5] w-full place-items-center gap-3 rounded-[1rem];
|
||||||
background: linear-gradient(135deg, #0f766e, #f97316);
|
background: linear-gradient(135deg, #0f766e, #f97316);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user