Compare commits

..

1 Commits

Author SHA1 Message Date
Philipinho 28347d0bfe noop audit module 2026-03-04 17:37:39 +00:00
8 changed files with 268 additions and 116 deletions
+2
View File
@@ -25,6 +25,7 @@ import { CacheModule } from '@nestjs/cache-manager';
import KeyvRedis from '@keyv/redis'; import KeyvRedis from '@keyv/redis';
import { LoggerModule } from './common/logger/logger.module'; import { LoggerModule } from './common/logger/logger.module';
import { ClsModule } from 'nestjs-cls'; import { ClsModule } from 'nestjs-cls';
import { NoopAuditModule } from './integrations/audit/audit.module';
const enterpriseModules = []; const enterpriseModules = [];
try { try {
@@ -47,6 +48,7 @@ try {
middleware: { mount: true }, middleware: { mount: true },
}), }),
LoggerModule, LoggerModule,
NoopAuditModule,
CoreModule, CoreModule,
DatabaseModule, DatabaseModule,
EnvironmentModule, EnvironmentModule,
-11
View File
@@ -20,10 +20,6 @@ import { AuditContextMiddleware } from '../common/middlewares/audit-context.midd
import { ShareModule } from './share/share.module'; import { ShareModule } from './share/share.module';
import { NotificationModule } from './notification/notification.module'; import { NotificationModule } from './notification/notification.module';
import { WatcherModule } from './watcher/watcher.module'; import { WatcherModule } from './watcher/watcher.module';
import {
AUDIT_SERVICE,
NoopAuditService,
} from '../integrations/audit/audit.service';
import { ClsMiddleware } from 'nestjs-cls'; import { ClsMiddleware } from 'nestjs-cls';
@Module({ @Module({
@@ -43,13 +39,6 @@ import { ClsMiddleware } from 'nestjs-cls';
NotificationModule, NotificationModule,
WatcherModule, WatcherModule,
], ],
providers: [
{
provide: AUDIT_SERVICE,
useClass: NoopAuditService,
},
],
exports: [AUDIT_SERVICE],
}) })
export class CoreModule implements NestModule { export class CoreModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
@@ -0,0 +1,14 @@
import { Global, Module } from '@nestjs/common';
import { AUDIT_SERVICE, NoopAuditService } from './audit.service';
@Global()
@Module({
providers: [
{
provide: AUDIT_SERVICE,
useClass: NoopAuditService,
},
],
exports: [AUDIT_SERVICE],
})
export class NoopAuditModule {}
+62 -8
View File
@@ -1,12 +1,7 @@
import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core"; import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core";
import type { ResizableNodeViewDirection } from "@tiptap/core"; import type { ResizableNodeViewDirection } from "@tiptap/core";
import { ReactNodeViewRenderer } from "@tiptap/react"; import { ReactNodeViewRenderer } from "@tiptap/react";
import { import { normalizeFileUrl } from "./media-utils";
normalizeFileUrl,
applyAlignment,
createPlaceholderView,
setupMediaLoading,
} from "./media-utils";
export type DrawioResizeOptions = { export type DrawioResizeOptions = {
enabled: boolean; enabled: boolean;
@@ -210,7 +205,22 @@ export const Drawio = Node.create<DrawioOptions>({
const { node, getPos, HTMLAttributes, editor } = props; const { node, getPos, HTMLAttributes, editor } = props;
if (!node.attrs.src) { if (!node.attrs.src) {
return createPlaceholderView(this.options.view, props); editor.isInitialized = true;
const reactView = ReactNodeViewRenderer(this.options.view);
const view = reactView(props);
const originalUpdate = view.update?.bind(view);
view.update = (updatedNode, decorations, innerDecorations) => {
if (updatedNode.attrs.src && !node.attrs.src) {
return false;
}
if (originalUpdate) {
return originalUpdate(updatedNode, decorations, innerDecorations);
}
return true;
};
return view;
} }
const el = document.createElement("img"); const el = document.createElement("img");
@@ -281,10 +291,54 @@ export const Drawio = Node.create<DrawioOptions>({
}, },
}); });
setupMediaLoading(nodeView.dom as HTMLElement, el, node); const dom = nodeView.dom as HTMLElement;
applyAlignment(dom, node.attrs.align || "center");
// Handle percentage width backward compat
const widthAttr = node.attrs.width;
if (typeof widthAttr === "string" && widthAttr.endsWith("%")) {
requestAnimationFrame(() => {
const parentEl = dom.parentElement;
if (parentEl) {
const containerWidth = parentEl.clientWidth;
const pctValue = parseInt(widthAttr, 10);
if (!isNaN(pctValue) && containerWidth > 0) {
const pxWidth = Math.round(
containerWidth * (pctValue / 100),
);
el.style.width = `${pxWidth}px`;
if (node.attrs.aspectRatio) {
el.style.height = `${Math.round(pxWidth / node.attrs.aspectRatio)}px`;
}
}
}
dom.style.visibility = "";
dom.style.pointerEvents = "";
});
}
// Show skeleton background while image loads from server
dom.style.pointerEvents = "none";
dom.style.background =
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
el.onload = () => {
dom.style.pointerEvents = "";
dom.style.background = "";
};
return nodeView; return nodeView;
}; };
}, },
}); });
function applyAlignment(container: HTMLElement, align: string) {
if (align === "left") {
container.style.justifyContent = "flex-start";
} else if (align === "right") {
container.style.justifyContent = "flex-end";
} else {
container.style.justifyContent = "center";
}
}
+62 -8
View File
@@ -1,12 +1,7 @@
import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core"; import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core";
import type { ResizableNodeViewDirection } from "@tiptap/core"; import type { ResizableNodeViewDirection } from "@tiptap/core";
import { ReactNodeViewRenderer } from "@tiptap/react"; import { ReactNodeViewRenderer } from "@tiptap/react";
import { import { normalizeFileUrl } from "./media-utils";
normalizeFileUrl,
applyAlignment,
createPlaceholderView,
setupMediaLoading,
} from "./media-utils";
export type ExcalidrawResizeOptions = { export type ExcalidrawResizeOptions = {
enabled: boolean; enabled: boolean;
@@ -210,7 +205,22 @@ export const Excalidraw = Node.create<ExcalidrawOptions>({
const { node, getPos, HTMLAttributes, editor } = props; const { node, getPos, HTMLAttributes, editor } = props;
if (!node.attrs.src) { if (!node.attrs.src) {
return createPlaceholderView(this.options.view, props); editor.isInitialized = true;
const reactView = ReactNodeViewRenderer(this.options.view);
const view = reactView(props);
const originalUpdate = view.update?.bind(view);
view.update = (updatedNode, decorations, innerDecorations) => {
if (updatedNode.attrs.src && !node.attrs.src) {
return false;
}
if (originalUpdate) {
return originalUpdate(updatedNode, decorations, innerDecorations);
}
return true;
};
return view;
} }
const el = document.createElement("img"); const el = document.createElement("img");
@@ -281,10 +291,54 @@ export const Excalidraw = Node.create<ExcalidrawOptions>({
}, },
}); });
setupMediaLoading(nodeView.dom as HTMLElement, el, node); const dom = nodeView.dom as HTMLElement;
applyAlignment(dom, node.attrs.align || "center");
// Handle percentage width backward compat
const widthAttr = node.attrs.width;
if (typeof widthAttr === "string" && widthAttr.endsWith("%")) {
requestAnimationFrame(() => {
const parentEl = dom.parentElement;
if (parentEl) {
const containerWidth = parentEl.clientWidth;
const pctValue = parseInt(widthAttr, 10);
if (!isNaN(pctValue) && containerWidth > 0) {
const pxWidth = Math.round(
containerWidth * (pctValue / 100),
);
el.style.width = `${pxWidth}px`;
if (node.attrs.aspectRatio) {
el.style.height = `${Math.round(pxWidth / node.attrs.aspectRatio)}px`;
}
}
}
dom.style.visibility = "";
dom.style.pointerEvents = "";
});
}
// Show skeleton background while image loads from server
dom.style.pointerEvents = "none";
dom.style.background =
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
el.onload = () => {
dom.style.pointerEvents = "";
dom.style.background = "";
};
return nodeView; return nodeView;
}; };
}, },
}); });
function applyAlignment(container: HTMLElement, align: string) {
if (align === "left") {
container.style.justifyContent = "flex-start";
} else if (align === "right") {
container.style.justifyContent = "flex-end";
} else {
container.style.justifyContent = "center";
}
}
+66 -8
View File
@@ -6,12 +6,7 @@ import {
Range, Range,
ResizableNodeView, ResizableNodeView,
} from "@tiptap/core"; } from "@tiptap/core";
import { import { normalizeFileUrl } from "../media-utils";
normalizeFileUrl,
applyAlignment,
createPlaceholderView,
setupMediaLoading,
} from "../media-utils";
import type { ResizableNodeViewDirection } from "@tiptap/core"; import type { ResizableNodeViewDirection } from "@tiptap/core";
export type ImageResizeOptions = { export type ImageResizeOptions = {
@@ -221,8 +216,25 @@ export const TiptapImage = Image.extend<ImageOptions>({
return (props) => { return (props) => {
const { node, getPos, HTMLAttributes, editor } = props; const { node, getPos, HTMLAttributes, editor } = props;
// If no src yet (placeholder/uploading), use React view for loading UI
if (!HTMLAttributes.src) { if (!HTMLAttributes.src) {
return createPlaceholderView(this.options.view, props); editor.isInitialized = true;
const reactView = ReactNodeViewRenderer(this.options.view);
const view = reactView(props);
// When the node gets a src, return false from update to force rebuild
const originalUpdate = view.update?.bind(view);
view.update = (updatedNode, decorations, innerDecorations) => {
if (updatedNode.attrs.src && !node.attrs.src) {
return false;
}
if (originalUpdate) {
return originalUpdate(updatedNode, decorations, innerDecorations);
}
return true;
};
return view;
} }
// Has src — use ResizableNodeView // Has src — use ResizableNodeView
@@ -319,10 +331,56 @@ export const TiptapImage = Image.extend<ImageOptions>({
}, },
}); });
setupMediaLoading(nodeView.dom as HTMLElement, el, node); const dom = nodeView.dom as HTMLElement;
// Apply initial alignment
applyAlignment(dom, node.attrs.align || "center");
// Handle percentage width backward compat
const widthAttr = node.attrs.width;
if (typeof widthAttr === "string" && widthAttr.endsWith("%")) {
// Defer conversion until we can measure the container
requestAnimationFrame(() => {
const parentEl = dom.parentElement;
if (parentEl) {
const containerWidth = parentEl.clientWidth;
const pctValue = parseInt(widthAttr, 10);
if (!isNaN(pctValue) && containerWidth > 0) {
const pxWidth = Math.round(
containerWidth * (pctValue / 100),
);
el.style.width = `${pxWidth}px`;
if (node.attrs.aspectRatio) {
el.style.height = `${Math.round(pxWidth / node.attrs.aspectRatio)}px`;
}
}
}
dom.style.visibility = "";
dom.style.pointerEvents = "";
});
}
// Show skeleton background while image loads from server
dom.style.pointerEvents = "none";
dom.style.background =
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
el.onload = () => {
dom.style.pointerEvents = "";
dom.style.background = "";
};
return nodeView; return nodeView;
}; };
}, },
}); });
function applyAlignment(container: HTMLElement, align: string) {
if (align === "left") {
container.style.justifyContent = "flex-start";
} else if (align === "right") {
container.style.justifyContent = "flex-end";
} else {
container.style.justifyContent = "center";
}
}
@@ -1,5 +1,4 @@
import { Editor } from "@tiptap/core"; import { Editor } from "@tiptap/core";
import { ReactNodeViewRenderer } from "@tiptap/react";
export function normalizeFileUrl(src: string): string { export function normalizeFileUrl(src: string): string {
if (src && src.startsWith("/files/")) { if (src && src.startsWith("/files/")) {
@@ -8,78 +7,6 @@ export function normalizeFileUrl(src: string): string {
return src || ""; return src || "";
} }
export function applyAlignment(container: HTMLElement, align: string) {
if (align === "left") {
container.style.justifyContent = "flex-start";
} else if (align === "right") {
container.style.justifyContent = "flex-end";
} else {
container.style.justifyContent = "center";
}
}
export function createPlaceholderView(viewComponent: any, props: any) {
const { node, editor } = props;
editor.isInitialized = true;
const reactView = ReactNodeViewRenderer(viewComponent);
const view = reactView(props);
const originalUpdate = view.update?.bind(view);
view.update = (updatedNode: any, decorations: any, innerDecorations: any) => {
if (updatedNode.attrs.src && !node.attrs.src) {
return false;
}
if (originalUpdate) {
return originalUpdate(updatedNode, decorations, innerDecorations);
}
return true;
};
return view;
}
export function setupMediaLoading(
dom: HTMLElement,
el: HTMLElement,
node: any,
loadEvent: "load" | "loadedmetadata" = "load",
) {
applyAlignment(dom, node.attrs.align || "center");
const widthAttr = node.attrs.width;
if (typeof widthAttr === "string" && widthAttr.endsWith("%")) {
requestAnimationFrame(() => {
const parentEl = dom.parentElement;
if (parentEl) {
const containerWidth = parentEl.clientWidth;
const pctValue = parseInt(widthAttr, 10);
if (!isNaN(pctValue) && containerWidth > 0) {
const pxWidth = Math.round(containerWidth * (pctValue / 100));
el.style.width = `${pxWidth}px`;
if (node.attrs.aspectRatio) {
el.style.height = `${Math.round(pxWidth / node.attrs.aspectRatio)}px`;
}
}
}
dom.style.visibility = "";
dom.style.pointerEvents = "";
});
}
dom.style.pointerEvents = "none";
dom.style.background =
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
el.addEventListener(
loadEvent,
() => {
dom.style.pointerEvents = "";
dom.style.background = "";
},
{ once: true },
);
}
export type UploadFn = ( export type UploadFn = (
file: File, file: File,
editor: Editor, editor: Editor,
+62 -8
View File
@@ -1,11 +1,6 @@
import { ReactNodeViewRenderer } from "@tiptap/react"; import { ReactNodeViewRenderer } from "@tiptap/react";
import { Range, Node, mergeAttributes, ResizableNodeView } from "@tiptap/core"; import { Range, Node, mergeAttributes, ResizableNodeView } from "@tiptap/core";
import { import { normalizeFileUrl } from "../media-utils";
normalizeFileUrl,
applyAlignment,
createPlaceholderView,
setupMediaLoading,
} from "../media-utils";
import type { ResizableNodeViewDirection } from "@tiptap/core"; import type { ResizableNodeViewDirection } from "@tiptap/core";
export type VideoResizeOptions = { export type VideoResizeOptions = {
@@ -210,7 +205,22 @@ export const TiptapVideo = Node.create<VideoOptions>({
const { node, getPos, HTMLAttributes, editor } = props; const { node, getPos, HTMLAttributes, editor } = props;
if (!node.attrs.src) { if (!node.attrs.src) {
return createPlaceholderView(this.options.view, props); editor.isInitialized = true;
const reactView = ReactNodeViewRenderer(this.options.view);
const view = reactView(props);
const originalUpdate = view.update?.bind(view);
view.update = (updatedNode, decorations, innerDecorations) => {
if (updatedNode.attrs.src && !node.attrs.src) {
return false;
}
if (originalUpdate) {
return originalUpdate(updatedNode, decorations, innerDecorations);
}
return true;
};
return view;
} }
const el = document.createElement("video"); const el = document.createElement("video");
@@ -289,10 +299,54 @@ export const TiptapVideo = Node.create<VideoOptions>({
}, },
}); });
setupMediaLoading(nodeView.dom as HTMLElement, el, node, "loadedmetadata"); const dom = nodeView.dom as HTMLElement;
applyAlignment(dom, node.attrs.align || "center");
// Handle percentage width backward compat
const widthAttr = node.attrs.width;
if (typeof widthAttr === "string" && widthAttr.endsWith("%")) {
requestAnimationFrame(() => {
const parentEl = dom.parentElement;
if (parentEl) {
const containerWidth = parentEl.clientWidth;
const pctValue = parseInt(widthAttr, 10);
if (!isNaN(pctValue) && containerWidth > 0) {
const pxWidth = Math.round(
containerWidth * (pctValue / 100),
);
el.style.width = `${pxWidth}px`;
if (node.attrs.aspectRatio) {
el.style.height = `${Math.round(pxWidth / node.attrs.aspectRatio)}px`;
}
}
}
dom.style.visibility = "";
dom.style.pointerEvents = "";
});
}
// Show skeleton background while video loads from server
dom.style.pointerEvents = "none";
dom.style.background =
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
el.onloadedmetadata = () => {
dom.style.pointerEvents = "";
dom.style.background = "";
};
return nodeView; return nodeView;
}; };
}, },
}); });
function applyAlignment(container: HTMLElement, align: string) {
if (align === "left") {
container.style.justifyContent = "flex-start";
} else if (align === "right") {
container.style.justifyContent = "flex-end";
} else {
container.style.justifyContent = "center";
}
}