44
app/controllers/api/v1/markers_controller.rb
Normal file
44
app/controllers/api/v1/markers_controller.rb
Normal file
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::MarkersController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:index]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, except: [:index]
|
||||
|
||||
before_action :require_user!
|
||||
|
||||
def index
|
||||
@markers = current_user.markers.where(timeline: Array(params[:timeline])).each_with_object({}) { |marker, h| h[marker.timeline] = marker }
|
||||
render json: serialize_map(@markers)
|
||||
end
|
||||
|
||||
def create
|
||||
Marker.transaction do
|
||||
@markers = {}
|
||||
|
||||
resource_params.each_pair do |timeline, timeline_params|
|
||||
@markers[timeline] = current_user.markers.find_or_initialize_by(timeline: timeline)
|
||||
@markers[timeline].update!(timeline_params)
|
||||
end
|
||||
end
|
||||
|
||||
render json: serialize_map(@markers)
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
render json: { error: 'Conflict during update, please try again' }, status: 409
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serialize_map(map)
|
||||
serialized = {}
|
||||
|
||||
map.each_pair do |key, value|
|
||||
serialized[key] = ActiveModelSerializers::SerializableResource.new(value, serializer: REST::MarkerSerializer).as_json
|
||||
end
|
||||
|
||||
Oj.dump(serialized)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.slice(*Marker::TIMELINES).permit(*Marker::TIMELINES.map { |timeline| { timeline.to_sym => [:last_read_id] } })
|
||||
end
|
||||
end
|
30
app/javascript/mastodon/actions/markers.js
Normal file
30
app/javascript/mastodon/actions/markers.js
Normal file
@ -0,0 +1,30 @@
|
||||
export const submitMarkers = () => (dispatch, getState) => {
|
||||
const accessToken = getState().getIn(['meta', 'access_token'], '');
|
||||
const params = {};
|
||||
|
||||
const lastHomeId = getState().getIn(['timelines', 'home', 'items', 0]);
|
||||
const lastNotificationId = getState().getIn(['notifications', 'items', 0, 'id']);
|
||||
|
||||
if (lastHomeId) {
|
||||
params.home = {
|
||||
last_read_id: lastHomeId,
|
||||
};
|
||||
}
|
||||
|
||||
if (lastNotificationId) {
|
||||
params.notifications = {
|
||||
last_read_id: lastNotificationId,
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(params).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = new XMLHttpRequest();
|
||||
|
||||
client.open('POST', '/api/v1/markers', false);
|
||||
client.setRequestHeader('Content-Type', 'application/json');
|
||||
client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
client.send(JSON.stringify(params));
|
||||
};
|
@ -16,6 +16,7 @@ import { expandNotifications } from '../../actions/notifications';
|
||||
import { fetchFilters } from '../../actions/filters';
|
||||
import { clearHeight } from '../../actions/height_cache';
|
||||
import { focusApp, unfocusApp } from 'mastodon/actions/app';
|
||||
import { submitMarkers } from 'mastodon/actions/markers';
|
||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
|
||||
import UploadArea from './components/upload_area';
|
||||
import ColumnsAreaContainer from './containers/columns_area_container';
|
||||
@ -241,7 +242,9 @@ class UI extends React.PureComponent {
|
||||
};
|
||||
|
||||
handleBeforeUnload = e => {
|
||||
const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props;
|
||||
const { intl, dispatch, isComposing, hasComposingText, hasMediaAttachments } = this.props;
|
||||
|
||||
dispatch(submitMarkers());
|
||||
|
||||
if (isComposing && (hasComposingText || hasMediaAttachments)) {
|
||||
// Setting returnValue to any string causes confirmation dialog.
|
||||
|
23
app/models/marker.rb
Normal file
23
app/models/marker.rb
Normal file
@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: markers
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# user_id :bigint(8)
|
||||
# timeline :string default(""), not null
|
||||
# last_read_id :bigint(8) default(0), not null
|
||||
# lock_version :integer default(0), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class Marker < ApplicationRecord
|
||||
TIMELINES = %w(home notifications).freeze
|
||||
|
||||
belongs_to :user
|
||||
|
||||
validates :timeline, :last_read_id, presence: true
|
||||
validates :timeline, inclusion: { in: TIMELINES }
|
||||
end
|
@ -74,6 +74,7 @@ class User < ApplicationRecord
|
||||
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
|
||||
has_many :backups, inverse_of: :user
|
||||
has_many :invites, inverse_of: :user
|
||||
has_many :markers, inverse_of: :user, dependent: :destroy
|
||||
|
||||
has_one :invite_request, class_name: 'UserInviteRequest', inverse_of: :user, dependent: :destroy
|
||||
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
|
||||
|
13
app/serializers/rest/marker_serializer.rb
Normal file
13
app/serializers/rest/marker_serializer.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::MarkerSerializer < ActiveModel::Serializer
|
||||
attributes :last_read_id, :version, :updated_at
|
||||
|
||||
def last_read_id
|
||||
object.last_read_id.to_s
|
||||
end
|
||||
|
||||
def version
|
||||
object.lock_version
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user