Embed video, audio and pdf (#1008)

pull/1017/head
onespaceman 2024-08-02 10:30:39 -04:00 committed by GitHub
parent 357a50f820
commit 45166ccd93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 106 additions and 46 deletions

View File

@ -6,8 +6,9 @@ import { decoratorStateField } from "./util.ts";
import type { Client } from "../client.ts"; import type { Client } from "../client.ts";
import { isFederationPath, isLocalPath, resolvePath } from "$sb/lib/resolve.ts"; import { isFederationPath, isLocalPath, resolvePath } from "$sb/lib/resolve.ts";
import { parsePageRef } from "$sb/lib/page_ref.ts"; import { parsePageRef } from "$sb/lib/page_ref.ts";
import { mime } from "mimetypes";
type ImageDimensions = { type ContentDimensions = {
width?: number; width?: number;
height?: number; height?: number;
}; };
@ -16,7 +17,7 @@ class InlineContentWidget extends WidgetType {
constructor( constructor(
readonly url: string, readonly url: string,
readonly title: string, readonly title: string,
readonly dim: ImageDimensions | undefined, readonly dim: ContentDimensions | undefined,
readonly client: Client, readonly client: Client,
) { ) {
super(); super();
@ -28,45 +29,95 @@ class InlineContentWidget extends WidgetType {
} }
get estimatedHeight(): number { get estimatedHeight(): number {
const cachedHeight = this.client.getCachedWidgetHeight(`image:${this.url}`); const cachedHeight = this.client.getCachedWidgetHeight(
`content:${this.url}`,
);
return cachedHeight; return cachedHeight;
} }
toDOM() { toDOM() {
const img = document.createElement("img"); const div = document.createElement("div");
// console.log("Creating DOM", this.url); div.className = "sb-inline-content";
const cachedImageHeight = this.client.getCachedWidgetHeight( div.style.display = "block";
`image:${this.url}`, const mimeType = mime.getType(
this.url.substring(this.url.lastIndexOf(".") + 1),
); );
img.onload = () => {
// console.log("Loaded", this.url, "with height", img.height); if (!mimeType) {
if (img.height !== cachedImageHeight) { return div;
this.client.setCachedWidgetHeight(`image:${this.url}`, img.height);
}
};
img.src = this.url;
img.alt = this.title;
img.title = this.title;
img.style.display = "block";
img.className = "sb-inline-img";
if (this.dim) {
img.style.height = this.dim.height ? `${this.dim.height}px` : "";
img.style.width = this.dim.width ? `${this.dim.width}px` : "";
} else if (cachedImageHeight > 0) {
img.height = cachedImageHeight;
} }
return img; if (mimeType.startsWith("image/")) {
const img = document.createElement("img");
img.src = this.url;
img.alt = this.title;
this.setDim(img, "load");
div.appendChild(img);
} else if (mimeType.startsWith("video/")) {
const video = document.createElement("video");
video.src = this.url;
video.title = this.title;
video.controls = true;
video.autoplay = false;
this.setDim(video, "loadeddata");
div.appendChild(video);
} else if (mimeType.startsWith("audio/")) {
const audio = document.createElement("audio");
audio.src = this.url;
audio.title = this.title;
audio.controls = true;
audio.autoplay = false;
this.setDim(audio, "loadeddata");
div.appendChild(audio);
} else if (mimeType === "application/pdf") {
const embed = document.createElement("object");
embed.type = mimeType;
embed.data = this.url;
embed.style.width = "100%";
embed.style.height = "20em";
this.setDim(embed, "load");
div.appendChild(embed);
}
return div;
}
setDim(el: HTMLElement, event: string) {
const cachedContentHeight = this.client.getCachedWidgetHeight(
`content:${this.url}`,
);
el.addEventListener(event, () => {
if (el.clientHeight !== cachedContentHeight) {
this.client.setCachedWidgetHeight(
`content:${this.url}`,
el.clientHeight,
);
}
});
el.style.maxWidth = "100%";
if (this.dim) {
if (this.dim.height) {
el.style.height = `${this.dim.height}px`;
}
if (this.dim.width) {
el.style.width = `${this.dim.width}px`;
}
} else if (cachedContentHeight > 0) {
el.style.height = cachedContentHeight.toString();
}
} }
} }
// Parse an alias, possibly containing image dimensions into an object // Parse an alias, possibly containing dimensions into an object
// Formats supported: "alias", "alias|100", "alias|100x200", "100", "100x200" // Formats supported: "alias", "alias|100", "alias|100x200", "100", "100x200"
function parseAlias( function parseAlias(
text: string, text: string,
): { alias?: string; dim?: ImageDimensions } { ): { alias?: string; dim?: ContentDimensions } {
let alias: string | undefined; let alias: string | undefined;
let dim: ImageDimensions | undefined; let dim: ContentDimensions | undefined;
if (text.includes("|")) { if (text.includes("|")) {
const [aliasPart, dimPart] = text.split("|"); const [aliasPart, dimPart] = text.split("|");
alias = aliasPart; alias = aliasPart;
@ -94,7 +145,7 @@ function parseAlias(
return { alias, dim }; return { alias, dim };
} }
export function inlineImagesPlugin(client: Client) { export function inlineContentPlugin(client: Client) {
return decoratorStateField((state: EditorState) => { return decoratorStateField((state: EditorState) => {
const widgets: Range<Decoration>[] = []; const widgets: Range<Decoration>[] = [];
@ -106,7 +157,7 @@ export function inlineImagesPlugin(client: Client) {
const text = state.sliceDoc(node.from, node.to); const text = state.sliceDoc(node.from, node.to);
let [url, alias]: (string | null)[] = [null, null]; let [url, alias]: (string | null)[] = [null, null];
let dim: ImageDimensions | undefined; let dim: ContentDimensions | undefined;
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
if ((match = /!?\[([^\]]*)\]\((.+)\)/g.exec(text))) { if ((match = /!?\[([^\]]*)\]\((.+)\)/g.exec(text))) {
[/* fullMatch */, alias, url] = match; [/* fullMatch */, alias, url] = match;
@ -173,7 +224,7 @@ export function inlineImagesPlugin(client: Client) {
Decoration.widget({ Decoration.widget({
widget: new InlineContentWidget(url, alias, dim, client), widget: new InlineContentWidget(url, alias, dim, client),
block: true, block: true,
}).range(node.to), }).range(node.to + 1),
); );
}, },
}); });

View File

@ -27,7 +27,7 @@ import {
import { vim } from "@replit/codemirror-vim"; import { vim } from "@replit/codemirror-vim";
import { markdown } from "@codemirror/lang-markdown"; import { markdown } from "@codemirror/lang-markdown";
import type { Client } from "./client.ts"; import type { Client } from "./client.ts";
import { inlineImagesPlugin } from "./cm_plugins/inline_content.ts"; import { inlineContentPlugin } from "./cm_plugins/inline_content.ts";
import { cleanModePlugins } from "./cm_plugins/clean.ts"; import { cleanModePlugins } from "./cm_plugins/clean.ts";
import { lineWrapper } from "./cm_plugins/line_wrapper.ts"; import { lineWrapper } from "./cm_plugins/line_wrapper.ts";
import { createSmartQuoteKeyBindings } from "./cm_plugins/smart_quotes.ts"; import { createSmartQuoteKeyBindings } from "./cm_plugins/smart_quotes.ts";
@ -114,7 +114,7 @@ export function createEditorState(
} }
}, },
}), }),
inlineImagesPlugin(client), inlineContentPlugin(client),
codeCopyPlugin(client), codeCopyPlugin(client),
highlightSpecialChars(), highlightSpecialChars(),
history(), history(),

View File

@ -32,21 +32,27 @@
background-color: var(--editor-directive-background-color); background-color: var(--editor-directive-background-color);
} }
.sb-line-h1, h1 { .sb-line-h1,
h1 {
font-size: 1.5em; font-size: 1.5em;
} }
.sb-line-h2, h2 { .sb-line-h2,
h2 {
font-size: 1.2em; font-size: 1.2em;
} }
.sb-line-h3, h3 { .sb-line-h3,
h3 {
font-size: 1.1em; font-size: 1.1em;
} }
.sb-line-h4, h4, .sb-line-h4,
.sb-line-h5, h5, h4,
.sb-line-h6, h6 { .sb-line-h5,
h5,
.sb-line-h6,
h6 {
font-size: 1em; font-size: 1em;
} }
@ -61,8 +67,9 @@
} }
} }
.sb-inline-img { .sb-inline-content * {
max-width: 100%; max-width: 100%;
display: block;
} }
.cm-panels-bottom .cm-vim-panel { .cm-panels-bottom .cm-vim-panel {
@ -639,4 +646,4 @@
div:not(.cm-focused).cm-fat-cursor { div:not(.cm-focused).cm-fat-cursor {
outline: none !important; outline: none !important;
} }

View File

@ -15,17 +15,19 @@ Attachments can be linked to in two ways:
* Via the wiki link syntax: `[[attachment.pdf]]`. These paths are absolute and relative to your spaces root, just like regular page links. That is: on a page `MyFolder/Hello` an attachment link `[[attachment.pdf]]` would link to the file `attachment.pdf` in the spaces root folder. * Via the wiki link syntax: `[[attachment.pdf]]`. These paths are absolute and relative to your spaces root, just like regular page links. That is: on a page `MyFolder/Hello` an attachment link `[[attachment.pdf]]` would link to the file `attachment.pdf` in the spaces root folder.
# Embedding # Embedding
Images can also be embedded using the [[#Linking]] syntax, but prefixed with an `!`: Media can also be embedded using the [[#Linking]] syntax, but prefixed with an `!`:
Images, videos, audio and PDFs are currently supported.
* `![alternate text](image.png)` * `![alternate text](image.png)`
* `![[image.png]]` * `![[image.png]]`
These follow the same relative/absolute path rules as links described before. These follow the same relative/absolute path rules as links described before.
## Image resizing ## Media resizing
In addition, images can be _sized_ using the following syntax: In addition, media can be _sized_ using the following syntax:
* Specifying only a width: `![Alt text|300](image.png)` or `![[image.png|300]]` * Specifying only a width: `![Alt text|300](image.png)` or `![[image.png|300]]`
* Specifying only a height: `![Alt text|x300](image.png)` or `![[image.png|x300]]`
* Specifying both width and height: `![Hello|300x300](image.png)` or `![[image.png|300x300]]` * Specifying both width and height: `![Hello|300x300](image.png)` or `![[image.png|300x300]]`
# Management # Management

View File

@ -2,11 +2,11 @@ Transclusions are an extension of the [[Markdown]] syntax enabling inline embedd
The general syntax is `![[path]]`. Two types of transclusions are currently supported: The general syntax is `![[path]]`. Two types of transclusions are currently supported:
# Images # Media
Syntax: `![[path/to/image.jpg]]` see [[Attachments#Embedding]] for more details. Syntax: `![[path/to/image.jpg]]` see [[Attachments#Embedding]] for more details.
Image resizing is also supported: Media resizing is also supported:
![[Attachments#Image resizing]] ![[Attachments#Media resizing]]
# Pages # Pages
Syntax: Syntax:
* `![[page name]]` embed an entire page * `![[page name]]` embed an entire page