feat: add image resize support in articles (#14293)
This commit is contained in:
@@ -61,7 +61,7 @@ const handleChange = () => {
|
|||||||
<div class="flex flex-col gap-2 items-start">
|
<div class="flex flex-col gap-2 items-start">
|
||||||
<div class="flex items-center justify-between w-full gap-3">
|
<div class="flex items-center justify-between w-full gap-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h3 class="text-heading-2 text-n-slate-12">
|
<h3 class="text-heading-3 text-n-slate-12">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</h3>
|
</h3>
|
||||||
<Label v-if="disabled" :label="disabledLabel" color="amber" compact />
|
<Label v-if="disabled" :label="disabledLabel" color="amber" compact />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ArticleMarkdownTransformer,
|
ArticleMarkdownTransformer,
|
||||||
EditorState,
|
EditorState,
|
||||||
Selection,
|
Selection,
|
||||||
|
imageResizeView,
|
||||||
} from '@chatwoot/prosemirror-schema';
|
} from '@chatwoot/prosemirror-schema';
|
||||||
import {
|
import {
|
||||||
suggestionsPlugin,
|
suggestionsPlugin,
|
||||||
@@ -235,6 +236,7 @@ export default {
|
|||||||
const tr = editorView.state.tr.replaceSelectionWith(tableNode);
|
const tr = editorView.state.tr.replaceSelectionWith(tableNode);
|
||||||
editorView.dispatch(tr.scrollIntoView());
|
editorView.dispatch(tr.scrollIntoView());
|
||||||
},
|
},
|
||||||
|
imageUpload: () => this.openFileBrowser(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const command = commandMap[actionKey];
|
const command = commandMap[actionKey];
|
||||||
@@ -332,6 +334,9 @@ export default {
|
|||||||
createEditorView() {
|
createEditorView() {
|
||||||
editorView = new EditorView(this.$refs.editor, {
|
editorView = new EditorView(this.$refs.editor, {
|
||||||
state: state,
|
state: state,
|
||||||
|
nodeViews: {
|
||||||
|
image: imageResizeView,
|
||||||
|
},
|
||||||
dispatchTransaction: tx => {
|
dispatchTransaction: tx => {
|
||||||
state = state.apply(tx);
|
state = state.apply(tx);
|
||||||
editorView.updateState(state);
|
editorView.updateState(state);
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ const EDITOR_ACTIONS = [
|
|||||||
icon: 'i-lucide-table',
|
icon: 'i-lucide-table',
|
||||||
menuKey: 'insertTable',
|
menuKey: 'insertTable',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'imageUpload',
|
||||||
|
labelKey: 'SLASH_COMMANDS.IMAGE',
|
||||||
|
icon: 'i-lucide-image',
|
||||||
|
menuKey: 'imageUpload',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'strike',
|
value: 'strike',
|
||||||
labelKey: 'SLASH_COMMANDS.STRIKETHROUGH',
|
labelKey: 'SLASH_COMMANDS.STRIKETHROUGH',
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
"CODE": "Code",
|
"CODE": "Code",
|
||||||
"BULLET_LIST": "Bullet List",
|
"BULLET_LIST": "Bullet List",
|
||||||
"ORDERED_LIST": "Ordered List",
|
"ORDERED_LIST": "Ordered List",
|
||||||
"TABLE": "Table"
|
"TABLE": "Table",
|
||||||
|
"IMAGE": "Image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,31 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def image(node)
|
||||||
|
src = escape_href(node.url)
|
||||||
|
width = extract_image_width(src)
|
||||||
|
plain do
|
||||||
|
out(%(<img src="#{src}"))
|
||||||
|
out(' alt="', :children, '"')
|
||||||
|
out(%( title="#{escape_html(node.title)}")) if node.title.present?
|
||||||
|
out(%( style="width: #{width}; max-width: 100%; height: auto;")) if width
|
||||||
|
out(' />')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def extract_image_width(src)
|
||||||
|
query = URI.parse(src).query
|
||||||
|
raw = query && CGI.parse(query)['cw_image_width']&.first
|
||||||
|
return unless raw =~ /\A(\d+)px\z/
|
||||||
|
|
||||||
|
px = Regexp.last_match(1).to_i
|
||||||
|
"#{px}px" if px.between?(1, 2000)
|
||||||
|
rescue URI::InvalidURIError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def surrounded_by_empty_lines?(node)
|
def surrounded_by_empty_lines?(node)
|
||||||
prev_node_empty?(node.previous) && next_node_empty?(node.next)
|
prev_node_empty?(node.previous) && next_node_empty?(node.next)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"@amplitude/analytics-browser": "^2.11.10",
|
"@amplitude/analytics-browser": "^2.11.10",
|
||||||
"@breezystack/lamejs": "^1.2.7",
|
"@breezystack/lamejs": "^1.2.7",
|
||||||
"@chatwoot/ninja-keys": "1.2.3",
|
"@chatwoot/ninja-keys": "1.2.3",
|
||||||
"@chatwoot/prosemirror-schema": "1.3.11",
|
"@chatwoot/prosemirror-schema": "1.3.13",
|
||||||
"@chatwoot/utils": "^0.0.52",
|
"@chatwoot/utils": "^0.0.52",
|
||||||
"@formkit/core": "^1.7.2",
|
"@formkit/core": "^1.7.2",
|
||||||
"@formkit/vue": "^1.7.2",
|
"@formkit/vue": "^1.7.2",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -26,8 +26,8 @@ importers:
|
|||||||
specifier: 1.2.3
|
specifier: 1.2.3
|
||||||
version: 1.2.3
|
version: 1.2.3
|
||||||
'@chatwoot/prosemirror-schema':
|
'@chatwoot/prosemirror-schema':
|
||||||
specifier: 1.3.11
|
specifier: 1.3.13
|
||||||
version: 1.3.11
|
version: 1.3.13
|
||||||
'@chatwoot/utils':
|
'@chatwoot/utils':
|
||||||
specifier: ^0.0.52
|
specifier: ^0.0.52
|
||||||
version: 0.0.52
|
version: 0.0.52
|
||||||
@@ -459,8 +459,8 @@ packages:
|
|||||||
'@chatwoot/ninja-keys@1.2.3':
|
'@chatwoot/ninja-keys@1.2.3':
|
||||||
resolution: {integrity: sha512-xM8d9P5ikDMZm2WbaCTk/TW5HFauylrU3cJ75fq5je6ixKwyhl/0kZbVN/vbbZN4+AUX/OaSIn6IJbtCgIF67g==}
|
resolution: {integrity: sha512-xM8d9P5ikDMZm2WbaCTk/TW5HFauylrU3cJ75fq5je6ixKwyhl/0kZbVN/vbbZN4+AUX/OaSIn6IJbtCgIF67g==}
|
||||||
|
|
||||||
'@chatwoot/prosemirror-schema@1.3.11':
|
'@chatwoot/prosemirror-schema@1.3.13':
|
||||||
resolution: {integrity: sha512-+GptIqY73/EtojrhAKX4UKwZF7NUB47DMzgYxmx8Vu07DLKCGe+8JnbHJnR9oBy8p5sn5VOO1ihNf/4Pg2RzIQ==}
|
resolution: {integrity: sha512-T6FBUinMJbwDCD7975g8M/Tsn2+G3O2pTGIXdcLkMRpbAAC6mVdl4ZcZektlt5y/PVmPVqNHPsfee1XB/C3vAw==}
|
||||||
|
|
||||||
'@chatwoot/utils@0.0.52':
|
'@chatwoot/utils@0.0.52':
|
||||||
resolution: {integrity: sha512-e57uVqyVW4tj1gql4YJPNMykqMJPkETn5Y9AmHdhc6Y7oxDXfRXBq27fZrrDadLkZdn5RYVCZjfIhXOumyYv2Q==}
|
resolution: {integrity: sha512-e57uVqyVW4tj1gql4YJPNMykqMJPkETn5Y9AmHdhc6Y7oxDXfRXBq27fZrrDadLkZdn5RYVCZjfIhXOumyYv2Q==}
|
||||||
@@ -4993,7 +4993,7 @@ snapshots:
|
|||||||
hotkeys-js: 3.8.7
|
hotkeys-js: 3.8.7
|
||||||
lit: 2.2.6
|
lit: 2.2.6
|
||||||
|
|
||||||
'@chatwoot/prosemirror-schema@1.3.11':
|
'@chatwoot/prosemirror-schema@1.3.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
markdown-it-sup: 2.0.0
|
markdown-it-sup: 2.0.0
|
||||||
prosemirror-commands: 1.7.1
|
prosemirror-commands: 1.7.1
|
||||||
|
|||||||
@@ -257,4 +257,18 @@ describe CustomMarkdownRenderer do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#image' do
|
||||||
|
it 'renders width in px with responsive cap and auto height' do
|
||||||
|
markdown = ''
|
||||||
|
expect(render_markdown(markdown)).to include(
|
||||||
|
'style="width: 400px; max-width: 100%; height: auto;"'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ignores a non-numeric width' do
|
||||||
|
markdown = ''
|
||||||
|
expect(render_markdown(markdown)).not_to include('style=')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user