Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
00855eb
fix(import): add rand import
LinkinStars Nov 14, 2025
da57274
fix: quick-links add divider
shuashuai Nov 21, 2025
23b2868
feat: add TypeScript configuration and Vite setup for editor stacks
robinv8 Dec 25, 2025
615a249
feat(editor-stacks): add internationalization support with English an…
robinv8 Dec 25, 2025
44d5932
fix: correct registration mode property in editor stacks
robinv8 Dec 25, 2025
34a3b58
chore: add Apache License header to global.d.ts, info.yaml, and vite.…
robinv8 Dec 25, 2025
96489ed
fix: adjust formatting in license comments for consistency
robinv8 Dec 25, 2025
5a0adfe
fix(info.yaml): update author and link fields for editor stacks
robinv8 Dec 25, 2025
17ee92a
fix(editor-stacks): refactor event handlers and improve cleanup in Co…
robinv8 Dec 25, 2025
ba8a24d
feat(editor-stacks): add onChange plugin for content monitoring and u…
robinv8 Dec 25, 2025
4495038
fix: quick-links update
shuashuai Dec 25, 2025
19b8134
Refactor code structure for improved readability and maintainability
robinv8 Dec 25, 2025
223636c
fix(editor-stacks/onChange-plugin): update import types and enhance c…
robinv8 Dec 25, 2025
b6a9486
Refactor code structure for improved readability and maintainability
robinv8 Dec 25, 2025
71fb6a2
fix(editor-stacks/Component): enhance onChange plugin to include edit…
robinv8 Dec 25, 2025
caf4692
fix(editor-stacks): remove registrationMode from info configuration
robinv8 Dec 26, 2025
45d0c0a
fix(editor-stacks/Component): add editor help link to component props
robinv8 Dec 26, 2025
35b3aa3
chore: Sync Plugin Info
robinv8 Dec 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion captcha-basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"author": "Answer.dev",
"description": "Basic for captcha",
"version": "1.0.5",
"version": "1.0.6",
"files": [
"dist",
"README.md"
Expand Down
2 changes: 1 addition & 1 deletion captcha-google-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"author": "Answer.dev",
"description": "google reCaptcha v2",
"version": "1.0.5",
"version": "1.0.6",
"files": [
"dist",
"README.md"
Expand Down
1 change: 1 addition & 0 deletions connector-basic/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"encoding/json"
"fmt"
"io"
"math/rand"
"regexp"
"strings"
"time"
Expand Down
2 changes: 1 addition & 1 deletion connector-basic/info.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@

slug_name: basic_connector
type: connector
version: 1.2.11
version: 1.2.12
author: answerdev
link: https://github.com/apache/answer-plugins/tree/main/connector-basic
2 changes: 1 addition & 1 deletion connector-wallet/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "connector-wallet",
"version": "1.0.1",
"version": "1.0.2",
"description": "Connect to Web3 wallet for third-party login",
"author": "Ourai L. <ourairyu@gmail.com>",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion editor-chart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "editor-chart",
"private": true,
"author": "Answer.dev",
"version": "1.2.11",
"version": "1.2.13",
"files": [
"dist",
"README.md"
Expand Down
2 changes: 1 addition & 1 deletion editor-formula/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "editor-formula",
"private": true,
"author": "Answer.dev",
"version": "1.2.14",
"version": "1.2.15",
"files": [
"dist",
"README.md"
Expand Down
222 changes: 222 additions & 0 deletions editor-stacks/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { FC, useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';

import { StacksEditor } from '@stackoverflow/stacks-editor';

import '@stackoverflow/stacks';
import '@stackoverflow/stacks/dist/css/stacks.css';

import '@stackoverflow/stacks-editor/dist/styles.css';
import { createOnChangePlugin } from './onChange-plugin';

export interface EditorProps {
value: string;
onChange?: (value: string) => void;
onFocus?: () => void;
onBlur?: () => void;
placeholder?: string;
autoFocus?: boolean;
imageUploadHandler?: (file: File | string) => Promise<string>;
uploadConfig?: {
maxImageSizeMiB?: number;
allowedExtensions?: string[];
};
}

const Component: FC<EditorProps> = ({
value,
onChange,
onFocus,
onBlur,
placeholder = '',
autoFocus = false,
imageUploadHandler,
uploadConfig,
}) => {
const { t } = useTranslation('plugin', {
keyPrefix: 'editor_stacks.frontend',
});
const containerRef = useRef<HTMLDivElement>(null);
const editorInstanceRef = useRef<StacksEditor | null>(null);
const isInitializedRef = useRef(false);
const onChangeRef = useRef(onChange);
const onFocusRef = useRef(onFocus);
const onBlurRef = useRef(onBlur);
const autoFocusTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
null,
);

// Version compatibility temporarily disabled

useEffect(() => {
onChangeRef.current = onChange;
onFocusRef.current = onFocus;
onBlurRef.current = onBlur;
});

const syncTheme = useCallback(() => {
if (!containerRef.current) return;

containerRef.current?.classList.remove(
'theme-light',
'theme-dark',
'theme-system',
);
const themeAttr =
document.documentElement.getAttribute('data-bs-theme') ||
document.body.getAttribute('data-bs-theme');

if (themeAttr) {
containerRef.current?.classList.add(`theme-${themeAttr}`);
}
}, []);

useEffect(() => {
syncTheme();
}, [syncTheme]);

useEffect(() => {
const observer = new MutationObserver(() => {
syncTheme();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-bs-theme', 'class'],
});
observer.observe(document.body, {
attributes: true,
attributeFilter: ['data-bs-theme', 'class'],
});
return () => observer.disconnect();
}, [syncTheme]);

useEffect(() => {
if (!containerRef.current || isInitializedRef.current) {
return;
}

let editorInstance: StacksEditor | null = null;

try {
editorInstance = new StacksEditor(containerRef.current, value || '', {
placeholderText: placeholder || t('placeholder', ''),
parserFeatures: {
tables: true,
html: false,
},
imageUpload: imageUploadHandler
? {
handler: imageUploadHandler,
sizeLimitMib: uploadConfig?.maxImageSizeMiB,
acceptedFileTypes: uploadConfig?.allowedExtensions,
}
: undefined,
editorHelpLink: 'https://stackoverflow.com/editing-help',
editorPlugins: onChange
? [
createOnChangePlugin(
() => editorInstanceRef.current,
(content: string) => {
onChangeRef.current?.(content);
}
),
]
: [],
});

editorInstanceRef.current = editorInstance;
isInitializedRef.current = true;

const editor = editorInstance;
const editorElement = editor.dom as HTMLElement;
const handleFocus = () => onFocusRef.current?.();
const handleBlur = () => onBlurRef.current?.();

if (editorElement) {
editorElement.addEventListener('focus', handleFocus, true);
editorElement.addEventListener('blur', handleBlur, true);
}

if (autoFocus) {
autoFocusTimeoutRef.current = setTimeout(() => {
if (editor) {
editor.focus();
}
}, 100);
}

return () => {
if (autoFocusTimeoutRef.current) {
clearTimeout(autoFocusTimeoutRef.current);
autoFocusTimeoutRef.current = null;
}

if (editorElement) {
editorElement.removeEventListener('focus', handleFocus, true);
editorElement.removeEventListener('blur', handleBlur, true);
}

if (editorInstance) {
try {
editorInstance.destroy();
} catch (e) {
console.error('Error destroying editor:', e);
}
}

editorInstanceRef.current = null;
isInitializedRef.current = false;

if (containerRef.current) {
containerRef.current.innerHTML = '';
}
};
} catch (error) {
console.error('Failed to initialize Stacks Editor:', error);
isInitializedRef.current = false;
}
}, []);

useEffect(() => {
const editor = editorInstanceRef.current;
if (!editor || !isInitializedRef.current) {
return;
}

try {
if (editor.content !== value) {
editor.content = value;
}
} catch (error) {
console.error('Error syncing editor content:', error);
}
}, [value]);

return (
<div
className="editor-stacks-wrapper editor-stacks-scope"
ref={containerRef}
/>
);
};

export default Component;
28 changes: 28 additions & 0 deletions editor-stacks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Editor-Stacks Plugin

A complete editor replacement plugin for Answer, built on [StackExchange/Stacks-Editor](https://github.com/StackExchange/Stacks-Editor).

## Features

- ✅ **Complete Editor Replacement**: Replaces Answer's default Markdown editor with a rich text editor experience
- 📝 **Markdown Support**: Seamless switching between rich text and Markdown editing modes
- 🎨 **Stack Overflow Style**: Built with the Stacks Design System for a familiar and professional look
- 🖼️ **Image Upload**: Supports drag-and-drop, paste from clipboard, and file picker for image uploads
- 📊 **Table Support**: Visual table editing with full Markdown table compatibility
- 🔧 **Rich Formatting**: Code blocks, blockquotes, lists, headings, bold, italic, and more
- ⌨️ **Keyboard Shortcuts**: Full keyboard support for power users
- 🌓 **Theme Sync**: Automatically adapts to Answer's light/dark theme settings

## Plugin Type

This plugin uses the `editor_replacement` type, which means:
- Only one editor replacement plugin can be active at a time
- It completely replaces the default editor interface
- All editor functionality is provided by the plugin

## Related Links

- [Stacks-Editor Documentation](https://github.com/StackExchange/Stacks-Editor)
- [Stacks Design System](https://stackoverflow.design/)


52 changes: 52 additions & 0 deletions editor-stacks/editor_stacks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package editor_stacks

import (
"embed"

"github.com/apache/answer-plugins/editor-stacks/i18n"
"github.com/apache/answer-plugins/util"
"github.com/apache/answer/plugin"
)

//go:embed info.yaml
var Info embed.FS

type EditorStacksPlugin struct {
}

func init() {
plugin.Register(&EditorStacksPlugin{})
}

func (d EditorStacksPlugin) Info() plugin.Info {
info := &util.Info{}
info.GetInfo(Info)

return plugin.Info{
Name: plugin.MakeTranslator(i18n.InfoName),
SlugName: info.SlugName,
Description: plugin.MakeTranslator(i18n.InfoDescription),
Author: info.Author,
Version: info.Version,
Link: info.Link,
}
}
23 changes: 23 additions & 0 deletions editor-stacks/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

declare module '*.css?raw' {
const content: string;
export default content;
}
Loading