Skip to content

Commit

Permalink
feat(post): Allow edit published thread and post
Browse files Browse the repository at this point in the history
This commit add support to edit publishd thread and post:
* Edit thread type/title/content.
* Edit post content.
* Configure additional options provided by server. Some options may
  require the text editor support which is not implement yet.
  • Loading branch information
realth000 committed Feb 19, 2024
1 parent bd8552e commit ef4c0bf
Show file tree
Hide file tree
Showing 15 changed files with 429 additions and 46 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.g.dart
*.mapper.dart
*.freezed.dart
/devtools_options.yaml
# Miscellaneous
*.class
*.log
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added

- 新增显示帖子的悬赏状态及对应的最佳答案。
- 新增支持编辑已发布的帖子和回复。
* 楼层右下角菜单 -> 编辑。
* 一些附加选项可能需要文本编辑器支持,目前可能不完全支持。
* 暂不支持设置阅读权限和售价。
- 在通知页内显示已有x条相同通知被忽略。
- 支持筛选积分变更历史。
- 帖子内按页数显示分组。
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@
* [ ] 回复表情
* [ ] 设置字体、字号、链接等
* [x] 回复其他楼层
* [ ] 编辑回复
* [x] 编辑回复
* [ ] 编辑帖子(一楼)
* [ ] 编辑帖子
* [ ] 修改纯文本内容
* [ ] 设置分类和标题
* [ ] 设置附加选项
* [x] *修改纯文本内容*
* [x] *设置分类和标题*
* [x] *设置附加选项*
* [ ] 设置阅读权限
* [ ] 设置售价
* [ ] 富文本模式
Expand All @@ -106,7 +106,7 @@
* [x] 按作者id和论坛id搜索
* [ ] 积分
* [x] 积分统计和历史记录
* [ ] *查询积分记录*
* [x] *查询积分记录*
* [ ] 购买
* [x] 购买帖子
* [x] 回复后可见
Expand Down
3 changes: 3 additions & 0 deletions lib/constants/layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const sizedBoxW5H5 = SizedBox(width: 5, height: 5);
/// A [SizedBox] with 10 width and 10 height.
const sizedBoxW10H10 = SizedBox(width: 10, height: 10);

/// A [SizedBox] with 15 width and 15 height.
const sizedBoxW15H15 = SizedBox(width: 15, height: 15);

/// A [SizedBox] with 20 width and 20 height.
const sizedBoxW20H20 = SizedBox(width: 20, height: 20);

Expand Down
67 changes: 57 additions & 10 deletions lib/features/post/bloc/post_edit_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
import 'package:dart_mappable/dart_mappable.dart';
import 'package:tsdm_client/exceptions/exceptions.dart';
import 'package:tsdm_client/extensions/string.dart';
import 'package:tsdm_client/features/post/exceptions/exceptions.dart';
import 'package:tsdm_client/features/post/models/post_edit_content.dart';
import 'package:tsdm_client/features/post/repository/post_edit_repository.dart';
import 'package:tsdm_client/utils/debug.dart';
Expand All @@ -21,6 +22,7 @@ final class PostEditBloc extends Bloc<PostEditEvent, PostEditState> {
: _postEditRepository = postEditRepository,
super(const PostEditState()) {
on<PostEditLoadDataRequested>(_onPostEditLoadDataRequested);
on<PostEditCompleteEditRequested>(_onPostEditCompleteEditRequested);
}

final PostEditRepository _postEditRepository;
Expand All @@ -34,13 +36,52 @@ final class PostEditBloc extends Bloc<PostEditEvent, PostEditState> {
final document = await _postEditRepository.fetchData(event.editUrl);
final content = _parseContent(document);
if (content == null) {
emit(state.copyWith(status: PostEditStatus.failed));
emit(state.copyWith(status: PostEditStatus.failedToLoad));
return;
}
emit(state.copyWith(status: PostEditStatus.success, content: content));
emit(state.copyWith(status: PostEditStatus.editing, content: content));
} on HttpRequestFailedException catch (e) {
debug('failed to load post edit data: $e');
emit(state.copyWith(status: PostEditStatus.failed));
emit(state.copyWith(status: PostEditStatus.failedToLoad));
}
}

Future<void> _onPostEditCompleteEditRequested(
PostEditCompleteEditRequested event,
PostEditEmit emit,
) async {
emit(state.copyWith(status: PostEditStatus.uploading));
try {
await _postEditRepository.postEditedContent(
formHash: event.formHash,
postTime: event.postTime,
delattachop: event.delattachop,
wysiwyg: event.wysiwyg,
fid: event.fid,
tid: event.tid,
pid: event.pid,
page: event.page,
threadType: event.threadType?.typeID,
threadTitle: event.threadTitle,
data: event.data,
options: Map.fromEntries(
event.options
.where((e) => !e.disabled && e.checked)
.map((e) => MapEntry(e.name, e.value)),
),
);
emit(state.copyWith(status: PostEditStatus.success));
} on HttpRequestFailedException catch (e) {
debug('failed to post edited post data: $e');
emit(state.copyWith(status: PostEditStatus.failedToUpload));
} on PostEditFailedToUploadResult catch (e) {
debug('failed to post edited post data: $e');
emit(
state.copyWith(
status: PostEditStatus.failedToUpload,
errorText: e.errorText,
),
);
}
}

Expand Down Expand Up @@ -139,13 +180,19 @@ final class PostEditBloc extends Bloc<PostEditEvent, PostEditState> {
e.querySelector('label') != null,
)
.map(
(e) => PostEditContentOption(
name: e.querySelector('input')!.id,
readableName: e.querySelector('label')!.innerText,
value: e.querySelector('input')!.attributes['value']!,
),
)
.toList();
(e) {
final input = e.querySelector('input')!;
final label = e.querySelector('label')!;

return PostEditContentOption(
name: input.id,
readableName: label.innerText,
disabled: input.attributes.containsKey('disabled'),
checked: input.attributes.containsKey('checked'),
value: input.attributes['value']!,
);
},
).toList();

if (formHash == null ||
postTime == null ||
Expand Down
62 changes: 61 additions & 1 deletion lib/features/post/bloc/post_edit_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,64 @@ final class PostEditLoadDataRequested extends PostEditEvent
/// User completed the editing and need to post to the server.
@MappableClass()
final class PostEditCompleteEditRequested extends PostEditEvent
with PostEditCompleteEditRequestedMappable {}
with PostEditCompleteEditRequestedMappable {
/// Constructor.
const PostEditCompleteEditRequested({
required this.formHash,
required this.postTime,
required this.delattachop,
required this.wysiwyg,
required this.fid,
required this.tid,
required this.pid,
required this.page,
required this.threadType,
required this.threadTitle,
required this.data,
required this.options,
}) : super();

/// Form hash.
final String formHash;

/// Post time.
final String postTime;

/// Delattachop.
final String delattachop;

/// What you see is what you get.
///
/// "0".
final String wysiwyg;

/// Forum id.
final String fid;

/// Thread id.
final String tid;

/// Post id.
final String pid;

/// Page.
///
/// Provided by server.
final String page;

/// Thread type.
///
/// Only needed when editing or creating a new thread.
final PostEditThreadType? threadType;

/// Thread title.
///
/// Only needed when editing or creating a new thread.
final String? threadTitle;

/// Post data, or first floor post data when submitting a thread.
final String data;

/// Additional options provided by server.
final List<PostEditContentOption> options;
}
23 changes: 19 additions & 4 deletions lib/features/post/bloc/post_edit_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ enum PostEditStatus {
/// Initial.
initial,

/// Loading data or posting edit result.
/// Loading data.
loading,

/// Failed to load data.
failedToLoad,

/// Waiting for user to edit.
editing,

/// Uploading data.
uploading,

/// Failed to load data.
failedToUpload,

/// Post edit result success.
success,

/// Failed to post the edit result to server.
failed,
}

/// State of mappable.
Expand All @@ -23,11 +32,17 @@ final class PostEditState with PostEditStateMappable {
const PostEditState({
this.status = PostEditStatus.initial,
this.content,
this.errorText,
});

/// Status.
final PostEditStatus status;

/// Post content.
final PostEditContent? content;

/// Error text html element.
///
/// Use this to show the error message.
final String? errorText;
}
14 changes: 14 additions & 0 deletions lib/features/post/exceptions/exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Exceptions used in editing post.
sealed class PostEditException implements Exception {
/// Constructor.
const PostEditException() : super();
}

/// Failed to upload edit result.
final class PostEditFailedToUploadResult extends PostEditException {
/// Constructor.
const PostEditFailedToUploadResult(this.errorText) : super();

/// Html element contains the error message.
final String errorText;
}
27 changes: 27 additions & 0 deletions lib/features/post/models/post_edit_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,42 @@ final class PostEditContentOption with PostEditContentOptionMappable {
const PostEditContentOption({
required this.name,
required this.value,
required this.disabled,
required this.checked,
required this.readableName,
});

/// Attribute "name".
///
/// This value is the name parameter in form.
final String name;

/// Attribute "value".
///
/// When this checkbox meets all the following conditions:
/// * Do not have [disabled] attribute.
/// * Have [checked] attribute.
///
/// This [value] will be added in form when submit to the server.
final String value;

/// Attribute "disabled".
///
/// Html checkbox "disabled" attribute.
///
/// Have this attribute (no matter has value or not) means the checkbox is
/// disabled.
final bool disabled;

/// Attribute "checked".
///
/// Have this attribute (no matter has value or not) means the checkbox is in
/// checked state.
///
/// When in checked state, the [value] will be added in form data when submit
/// form to server.
final bool checked;

/// Human readable name inside <input> node.
final String readableName;
}
Expand Down
Loading

0 comments on commit ef4c0bf

Please sign in to comment.