Skip to content

Commit e96450e

Browse files
committed
feature:支持图片粘贴功能
1 parent f81cba1 commit e96450e

File tree

6 files changed

+190
-62
lines changed

6 files changed

+190
-62
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/media/

jyy_slide_web/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@
130130
# 启用 WhiteNoise 的压缩和缓存功能(可选)
131131
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
132132

133+
MEDIA_URL = '/media/'
134+
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
133135
# Default primary key field type
134136
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
135137

jyy_slide_web/urls.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@
1717

1818
from django.contrib import admin
1919
from django.urls import path, include
20+
from django.conf import settings
21+
from django.conf.urls.static import static
2022

2123
urlpatterns = [
2224
path('', include('slideapp.urls')),
2325
path('admin/', admin.site.urls),
24-
]
26+
]
27+
28+
if settings.DEBUG:
29+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

slideapp/templates/index.html

Lines changed: 141 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,82 +3,164 @@
33
<head>
44
<title>JYY Slide Web</title>
55
<style>
6-
body { display: flex; height: 100vh; margin: 0; }
7-
#editor { width: 50%; height: 100%; }
8-
#preview { width: 50%; height: 100%; }
9-
iframe { width: 100%; height: 100%; border: none; }
6+
body {
7+
display: flex;
8+
height: 100vh;
9+
margin: 0;
10+
}
11+
12+
#editor {
13+
width: 50%;
14+
height: 100%;
15+
}
16+
17+
#preview {
18+
width: 50%;
19+
height: 100%;
20+
}
21+
22+
iframe {
23+
width: 100%;
24+
height: 100%;
25+
border: none;
26+
}
1027
</style>
1128
</head>
1229
<body>
13-
<textarea id="editor"># 欢迎使用 JYY Slide Web
30+
<textarea id="editor"># 欢迎使用 JYY Slide Web
1431

1532
开始编写您的 Markdown...</textarea>
16-
<div id="preview">
17-
<iframe id="preview-iframe"></iframe>
18-
</div>
33+
<div id="preview">
34+
<iframe id="preview-iframe"></iframe>
35+
</div>
1936

20-
<!-- 引入必要的 JavaScript 库,例如 jQuery -->
21-
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
37+
<!-- 引入必要的 JavaScript 库,例如 jQuery -->
38+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
2239

23-
<!-- 与 WebSocket 进行通信的脚本 -->
24-
<script>
25-
var socket = new WebSocket('ws://' + window.location.host + '/ws/slide/');
26-
var currentIndices = { h: 0, v: 0, f: undefined };
40+
<!-- 与 WebSocket 进行通信的脚本 -->
41+
<script>
42+
var socket = new WebSocket('ws://' + window.location.host + '/ws/slide/');
43+
var currentIndices = {h: 0, v: 0, f: undefined};
2744

28-
socket.onmessage = function(e) {
29-
var data = JSON.parse(e.data);
30-
var html_content = data['html'];
45+
socket.onmessage = function (e) {
46+
var data = JSON.parse(e.data);
47+
var html_content = data['html'];
3148

32-
var iframe = document.getElementById('preview-iframe');
49+
var iframe = document.getElementById('preview-iframe');
3350

34-
// 获取当前幻灯片的索引
35-
if (iframe.contentWindow && iframe.contentWindow.Reveal && iframe.contentWindow.Reveal.getIndices) {
36-
currentIndices = iframe.contentWindow.Reveal.getIndices();
37-
}
51+
// 获取当前幻灯片的索引
52+
if (iframe.contentWindow && iframe.contentWindow.Reveal && iframe.contentWindow.Reveal.getIndices) {
53+
currentIndices = iframe.contentWindow.Reveal.getIndices();
54+
}
55+
56+
// 更新预览 iframe 的内容
57+
iframe.contentDocument.open();
58+
iframe.contentDocument.write(html_content);
59+
iframe.contentDocument.close();
3860

39-
// 更新预览 iframe 的内容
40-
iframe.contentDocument.open();
41-
iframe.contentDocument.write(html_content);
42-
iframe.contentDocument.close();
43-
44-
// 等待 iframe 加载完成后,恢复幻灯片位置
45-
iframe.onload = function() {
46-
function restoreSlide() {
47-
if (iframe.contentWindow.Reveal && iframe.contentWindow.Reveal.isReady()) {
48-
iframe.contentWindow.Reveal.slide(currentIndices.h, currentIndices.v, currentIndices.f);
49-
50-
// 当幻灯片变化时,更新 currentIndices
51-
iframe.contentWindow.Reveal.on('slidechanged', function(event) {
52-
currentIndices = iframe.contentWindow.Reveal.getIndices();
53-
});
54-
} else {
55-
setTimeout(restoreSlide, 50);
56-
}
61+
// 等待 iframe 加载完成后,恢复幻灯片位置
62+
iframe.onload = function () {
63+
function restoreSlide() {
64+
if (iframe.contentWindow.Reveal && iframe.contentWindow.Reveal.isReady()) {
65+
iframe.contentWindow.Reveal.slide(currentIndices.h, currentIndices.v, currentIndices.f);
66+
67+
// 当幻灯片变化时,更新 currentIndices
68+
iframe.contentWindow.Reveal.on('slidechanged', function (event) {
69+
currentIndices = iframe.contentWindow.Reveal.getIndices();
70+
});
71+
} else {
72+
setTimeout(restoreSlide, 50);
5773
}
58-
restoreSlide();
59-
};
60-
};
74+
}
6175

62-
socket.onopen = function(e) {
63-
console.log('WebSocket 连接已打开');
64-
sendMarkdown();
76+
restoreSlide();
6577
};
78+
};
6679

67-
socket.onclose = function(e) {
68-
console.log('WebSocket 连接已关闭');
69-
};
80+
socket.onopen = function (e) {
81+
console.log('WebSocket 连接已打开');
82+
sendMarkdown();
83+
};
84+
85+
socket.onclose = function (e) {
86+
console.log('WebSocket 连接已关闭');
87+
};
88+
89+
// 监听编辑器内容的变化
90+
$('#editor').on('input', function () {
91+
sendMarkdown();
92+
});
7093

71-
// 监听编辑器内容的变化
72-
$('#editor').on('input', function() {
73-
sendMarkdown();
74-
});
94+
function sendMarkdown() {
95+
var markdown = $('#editor').val();
96+
socket.send(JSON.stringify({
97+
'markdown': markdown
98+
}));
99+
}
75100

76-
function sendMarkdown() {
77-
var markdown = $('#editor').val();
78-
socket.send(JSON.stringify({
79-
'markdown': markdown
80-
}));
101+
var editor = document.getElementById('editor');
102+
103+
// 监听粘贴事件
104+
editor.addEventListener('paste', function (e) {
105+
var items = (e.clipboardData || e.originalEvent.clipboardData).items;
106+
for (var i = 0; i < items.length; i++) {
107+
if (items[i].type.indexOf('image') !== -1) {
108+
var file = items[i].getAsFile();
109+
uploadImage(file);
110+
e.preventDefault();
111+
}
112+
}
113+
});
114+
115+
// 监听拖放事件
116+
editor.addEventListener('drop', function (e) {
117+
e.preventDefault();
118+
var files = e.dataTransfer.files;
119+
for (var i = 0; i < files.length; i++) {
120+
if (files[i].type.indexOf('image') !== -1) {
121+
uploadImage(files[i]);
122+
}
81123
}
82-
</script>
124+
});
125+
126+
function uploadImage(file) {
127+
var formData = new FormData();
128+
formData.append('image', file);
129+
130+
// 使用 AJAX 发送图片数据
131+
var xhr = new XMLHttpRequest();
132+
xhr.open('POST', '/upload_image/', true);
133+
134+
xhr.onload = function () {
135+
if (xhr.status === 200) {
136+
var response = JSON.parse(xhr.responseText);
137+
if (response.url) {
138+
insertImageMarkdown(response.url);
139+
} else {
140+
alert('上传失败');
141+
}
142+
} else {
143+
alert('上传失败');
144+
}
145+
};
146+
147+
xhr.send(formData);
148+
}
149+
150+
function insertImageMarkdown(url) {
151+
var markdownImage = '![](' + url + ')';
152+
153+
// 获取光标位置并插入文本
154+
var startPos = editor.selectionStart;
155+
var endPos = editor.selectionEnd;
156+
var value = editor.value;
157+
158+
editor.value = value.substring(0, startPos) + markdownImage + value.substring(endPos);
159+
editor.selectionStart = editor.selectionEnd = startPos + markdownImage.length;
160+
161+
// 触发输入事件,更新预览
162+
editor.dispatchEvent(new Event('input'));
163+
}
164+
</script>
83165
</body>
84166
</html>

slideapp/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
# slideapp/urls.py
2+
13
from django.urls import path
24
from . import views
35

46
urlpatterns = [
57
path('', views.index, name='index'),
8+
# 其他 URL 配置
9+
path('upload_image/', views.upload_image, name='upload_image'),
610
]

slideapp/views.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,41 @@
1+
from django.views.decorators.csrf import csrf_exempt
2+
from django.http import JsonResponse
3+
import os
4+
import uuid
5+
from django.conf import settings
16
from django.shortcuts import render
7+
from django.http import JsonResponse
8+
import os
9+
import uuid
10+
from django.conf import settings
11+
from imghdr import what
12+
13+
@csrf_exempt
14+
def upload_image(request):
15+
if request.method == 'POST':
16+
image = request.FILES.get('image')
17+
if image and image.content_type.startswith('image/'):
18+
# 生成唯一的文件名,防止冲突
19+
ext = os.path.splitext(image.name)[1]
20+
filename = uuid.uuid4().hex + ext
21+
filepath = os.path.join(settings.MEDIA_ROOT, 'uploads', filename)
22+
23+
# 确保上传目录存在
24+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
25+
26+
# 保存文件
27+
with open(filepath, 'wb+') as destination:
28+
for chunk in image.chunks():
29+
destination.write(chunk)
30+
31+
# 返回图片的访问 URL
32+
url = settings.MEDIA_URL + 'uploads/' + filename
33+
return JsonResponse({'url': url})
34+
else:
35+
return JsonResponse({'error': '无效的文件'}, status=400)
36+
else:
37+
return JsonResponse({'error': '不支持的请求方法'}, status=405)
238

3-
# Create your views here.
4-
from django.shortcuts import render
539

640
def index(request):
741
return render(request, 'index.html')

0 commit comments

Comments
 (0)