WIP: post editing #103
6 changed files with 183 additions and 101 deletions
|
@ -425,46 +425,4 @@ def object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def object_data_hashtags(_), do: []
|
def object_data_hashtags(_), do: []
|
||||||
|
|
||||||
def history_for(object) do
|
|
||||||
with history <- Map.get(object, "formerRepresentations"),
|
|
||||||
true <- is_map(history),
|
|
||||||
"OrderedCollection" <- Map.get(history, "type"),
|
|
||||||
true <- is_list(Map.get(history, "orderedItems")),
|
|
||||||
true <- is_integer(Map.get(history, "totalItems")) do
|
|
||||||
history
|
|
||||||
else
|
|
||||||
_ -> history_skeleton()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp history_skeleton do
|
|
||||||
%{
|
|
||||||
"type" => "OrderedCollection",
|
|
||||||
"totalItems" => 0,
|
|
||||||
"orderedItems" => []
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_update_history(updated_object, orig_object_data, updated) do
|
|
||||||
if not updated do
|
|
||||||
updated_object
|
|
||||||
else
|
|
||||||
# Put edit history
|
|
||||||
# Note that we may have got the edit history by first fetching the object
|
|
||||||
history = Object.history_for(orig_object_data)
|
|
||||||
|
|
||||||
latest_history_item =
|
|
||||||
orig_object_data
|
|
||||||
|> Map.drop(["id", "formerRepresentations"])
|
|
||||||
|
|
||||||
new_history =
|
|
||||||
history
|
|
||||||
|> Map.put("orderedItems", [latest_history_item | history["orderedItems"]])
|
|
||||||
|> Map.put("totalItems", history["totalItems"] + 1)
|
|
||||||
|
|
||||||
updated_object
|
|
||||||
|> Map.put("formerRepresentations", new_history)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,7 +50,14 @@ defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do
|
||||||
Pleroma.Constants.status_updatable_fields()
|
Pleroma.Constants.status_updatable_fields()
|
||||||
|> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end)
|
|> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end)
|
||||||
|
|
||||||
new_data |> Object.maybe_update_history(old_data, changed?)
|
%{updated_object: updated_object} =
|
||||||
|
new_data
|
||||||
|
|> Object.Updater.maybe_update_history(old_data,
|
||||||
|
updated: changed?,
|
||||||
|
use_history_in_new_object?: false
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_object
|
||||||
else
|
else
|
||||||
new_data
|
new_data
|
||||||
end
|
end
|
||||||
|
|
157
lib/pleroma/object/updater.ex
Normal file
157
lib/pleroma/object/updater.ex
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Object.Updater do
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
def update_content_fields(orig_object_data, updated_object) do
|
||||||
|
Pleroma.Constants.status_updatable_fields()
|
||||||
|
|> Enum.reduce(
|
||||||
|
%{data: orig_object_data, updated: false},
|
||||||
|
fn field, %{data: data, updated: updated} ->
|
||||||
|
updated = updated or Map.get(updated_object, field) != Map.get(orig_object_data, field)
|
||||||
|
|
||||||
|
data =
|
||||||
|
if Map.has_key?(updated_object, field) do
|
||||||
|
Map.put(data, field, updated_object[field])
|
||||||
|
else
|
||||||
|
Map.drop(data, [field])
|
||||||
|
end
|
||||||
|
|
||||||
|
%{data: data, updated: updated}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_history(object) do
|
||||||
|
with history <- Map.get(object, "formerRepresentations"),
|
||||||
|
true <- is_map(history),
|
||||||
|
"OrderedCollection" <- Map.get(history, "type"),
|
||||||
|
true <- is_list(Map.get(history, "orderedItems")),
|
||||||
|
true <- is_integer(Map.get(history, "totalItems")) do
|
||||||
|
history
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def history_for(object) do
|
||||||
|
with history when not is_nil(history) <- maybe_history(object) do
|
||||||
|
history
|
||||||
|
else
|
||||||
|
_ -> history_skeleton()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp history_skeleton do
|
||||||
|
%{
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"totalItems" => 0,
|
||||||
|
"orderedItems" => []
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_update_history(
|
||||||
|
updated_object,
|
||||||
|
orig_object_data,
|
||||||
|
opts
|
||||||
|
) do
|
||||||
|
updated = opts[:updated]
|
||||||
|
use_history_in_new_object? = opts[:use_history_in_new_object?]
|
||||||
|
|
||||||
|
if not updated do
|
||||||
|
%{updated_object: updated_object, used_history_in_new_object?: false}
|
||||||
|
else
|
||||||
|
# Put edit history
|
||||||
|
# Note that we may have got the edit history by first fetching the object
|
||||||
|
{new_history, used_history_in_new_object?} =
|
||||||
|
with true <- use_history_in_new_object?,
|
||||||
|
updated_history when not is_nil(updated_history) <- maybe_history(updated_object) do
|
||||||
|
{updated_history, true}
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
history = history_for(orig_object_data)
|
||||||
|
|
||||||
|
latest_history_item =
|
||||||
|
orig_object_data
|
||||||
|
|> Map.drop(["id", "formerRepresentations"])
|
||||||
|
|
||||||
|
updated_history =
|
||||||
|
history
|
||||||
|
|> Map.put("orderedItems", [latest_history_item | history["orderedItems"]])
|
||||||
|
|> Map.put("totalItems", history["totalItems"] + 1)
|
||||||
|
|
||||||
|
{updated_history, false}
|
||||||
|
end
|
||||||
|
|
||||||
|
updated_object =
|
||||||
|
updated_object
|
||||||
|
|> Map.put("formerRepresentations", new_history)
|
||||||
|
|
||||||
|
%{updated_object: updated_object, used_history_in_new_object?: used_history_in_new_object?}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_update_poll(to_be_updated, updated_object) do
|
||||||
|
choice_key = fn data ->
|
||||||
|
if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf"
|
||||||
|
end
|
||||||
|
|
||||||
|
with true <- to_be_updated["type"] == "Question",
|
||||||
|
key <- choice_key.(updated_object),
|
||||||
|
true <- key == choice_key.(to_be_updated),
|
||||||
|
orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])),
|
||||||
|
new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])),
|
||||||
|
true <- orig_choices == new_choices do
|
||||||
|
# Choices are the same, but counts are different
|
||||||
|
to_be_updated
|
||||||
|
|> Map.put(key, updated_object[key])
|
||||||
|
else
|
||||||
|
# Choices (or vote type) have changed, do not allow this
|
||||||
|
_ -> to_be_updated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This calculates the data to be sent as the object of an Update.
|
||||||
|
# new_data's formerRepresentations is not considered.
|
||||||
|
# formerRepresentations is added to the returned data.
|
||||||
|
def make_update_object_data(original_data, new_data, date) do
|
||||||
|
%{data: updated_data, updated: updated} =
|
||||||
|
original_data
|
||||||
|
|> update_content_fields(new_data)
|
||||||
|
|
||||||
|
if not updated do
|
||||||
|
updated_data
|
||||||
|
else
|
||||||
|
%{updated_object: updated_data} =
|
||||||
|
updated_data
|
||||||
|
|> maybe_update_history(original_data, updated: updated, use_history_in_new_object?: false)
|
||||||
|
|
||||||
|
updated_data
|
||||||
|
|> Map.put("updated", date)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This calculates the data of the new Object from an Update.
|
||||||
|
# new_data's formerRepresentations is considered.
|
||||||
|
def make_new_object_data_from_update_object(original_data, new_data) do
|
||||||
|
%{data: updated_data, updated: updated} =
|
||||||
|
original_data
|
||||||
|
|> update_content_fields(new_data)
|
||||||
|
|
||||||
|
%{updated_object: updated_data, used_history_in_new_object?: used_history_in_new_object?} =
|
||||||
|
updated_data
|
||||||
|
|> maybe_update_history(original_data, updated: updated, use_history_in_new_object?: false)
|
||||||
|
|
||||||
|
updated_data =
|
||||||
|
updated_data
|
||||||
|
|> maybe_update_poll(new_data)
|
||||||
|
|
||||||
|
%{
|
||||||
|
updated_data: updated_data,
|
||||||
|
updated: updated,
|
||||||
|
used_history_in_new_object?: used_history_in_new_object?
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -417,45 +417,6 @@ defp handle_update_user(
|
||||||
end
|
end
|
||||||
|
|
||||||
@updatable_object_types ["Note", "Question"]
|
@updatable_object_types ["Note", "Question"]
|
||||||
defp update_content_fields(orig_object_data, updated_object) do
|
|
||||||
Pleroma.Constants.status_updatable_fields()
|
|
||||||
|> Enum.reduce(
|
|
||||||
%{data: orig_object_data, updated: false},
|
|
||||||
fn field, %{data: data, updated: updated} ->
|
|
||||||
updated = updated or Map.get(updated_object, field) != Map.get(orig_object_data, field)
|
|
||||||
|
|
||||||
data =
|
|
||||||
if Map.has_key?(updated_object, field) do
|
|
||||||
Map.put(data, field, updated_object[field])
|
|
||||||
else
|
|
||||||
Map.drop(data, [field])
|
|
||||||
end
|
|
||||||
|
|
||||||
%{data: data, updated: updated}
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_update_poll(to_be_updated, updated_object) do
|
|
||||||
choice_key = fn data ->
|
|
||||||
if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf"
|
|
||||||
end
|
|
||||||
|
|
||||||
with true <- to_be_updated["type"] == "Question",
|
|
||||||
key <- choice_key.(updated_object),
|
|
||||||
true <- key == choice_key.(to_be_updated),
|
|
||||||
orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])),
|
|
||||||
new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])),
|
|
||||||
true <- orig_choices == new_choices do
|
|
||||||
# Choices are the same, but counts are different
|
|
||||||
to_be_updated
|
|
||||||
|> Map.put(key, updated_object[key])
|
|
||||||
else
|
|
||||||
# Choices (or vote type) have changed, do not allow this
|
|
||||||
_ -> to_be_updated
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_update_object(
|
defp handle_update_object(
|
||||||
%{data: %{"type" => "Update", "object" => updated_object}} = object,
|
%{data: %{"type" => "Update", "object" => updated_object}} = object,
|
||||||
meta
|
meta
|
||||||
|
@ -467,14 +428,11 @@ defp handle_update_object(
|
||||||
updated_object = meta[:object_data]
|
updated_object = meta[:object_data]
|
||||||
|
|
||||||
if orig_object_data["type"] in @updatable_object_types do
|
if orig_object_data["type"] in @updatable_object_types do
|
||||||
%{data: updated_object_data, updated: updated} =
|
%{
|
||||||
orig_object_data
|
updated_data: updated_object_data,
|
||||||
|> update_content_fields(updated_object)
|
updated: updated,
|
||||||
|
used_history_in_new_object?: used_history_in_new_object?
|
||||||
updated_object_data =
|
} = Object.Updater.make_new_object_data_from_update_object(orig_object_data, updated_object)
|
||||||
updated_object_data
|
|
||||||
|> Object.maybe_update_history(orig_object_data, updated)
|
|
||||||
|> maybe_update_poll(updated_object)
|
|
||||||
|
|
||||||
changeset =
|
changeset =
|
||||||
orig_object
|
orig_object
|
||||||
|
@ -486,6 +444,16 @@ defp handle_update_object(
|
||||||
{:ok, _} <- Object.set_cache(new_object),
|
{:ok, _} <- Object.set_cache(new_object),
|
||||||
# The metadata/utils.ex uses the object id for the cache.
|
# The metadata/utils.ex uses the object id for the cache.
|
||||||
{:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do
|
{:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do
|
||||||
|
if used_history_in_new_object? do
|
||||||
|
with create_activity when not is_nil(create_activity) <-
|
||||||
|
Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id),
|
||||||
|
{:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if updated do
|
if updated do
|
||||||
object
|
object
|
||||||
|> Activity.normalize()
|
|> Activity.normalize()
|
||||||
|
|
|
@ -366,15 +366,7 @@ defp make_update_data(user, orig_object, changes) do
|
||||||
|
|
||||||
with {:ok, draft} <- ActivityDraft.create(user, params) do
|
with {:ok, draft} <- ActivityDraft.create(user, params) do
|
||||||
change =
|
change =
|
||||||
Pleroma.Constants.status_updatable_fields()
|
Object.Updater.make_update_object_data(orig_object.data, draft.object, Utils.make_date())
|
||||||
|> Enum.reduce(orig_object.data, fn key, acc ->
|
|
||||||
if Map.has_key?(draft.object, key) do
|
|
||||||
acc |> Map.put(key, Map.get(draft.object, key))
|
|
||||||
else
|
|
||||||
acc |> Map.drop([key])
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> Map.put("updated", Utils.make_date())
|
|
||||||
|
|
||||||
{:ok, change}
|
{:ok, change}
|
||||||
else
|
else
|
||||||
|
|
|
@ -273,7 +273,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
|
|
||||||
history_len =
|
history_len =
|
||||||
1 +
|
1 +
|
||||||
(Object.history_for(object.data)
|
(Object.Updater.history_for(object.data)
|
||||||
|> Map.get("orderedItems")
|
|> Map.get("orderedItems")
|
||||||
|> length())
|
|> length())
|
||||||
|
|
||||||
|
@ -419,7 +419,7 @@ def render("history.json", %{activity: %{data: %{"object" => _object}} = activit
|
||||||
user = CommonAPI.get_user(activity.data["actor"])
|
user = CommonAPI.get_user(activity.data["actor"])
|
||||||
|
|
||||||
past_history =
|
past_history =
|
||||||
Object.history_for(object.data)
|
Object.Updater.history_for(object.data)
|
||||||
|> Map.get("orderedItems")
|
|> Map.get("orderedItems")
|
||||||
|> Enum.map(&Map.put(&1, "id", object.data["id"]))
|
|> Enum.map(&Map.put(&1, "id", object.data["id"]))
|
||||||
|> Enum.map(&%Object{data: &1, id: object.id})
|
|> Enum.map(&%Object{data: &1, id: object.id})
|
||||||
|
|
Loading…
Reference in a new issue